---
title: Why JSON Key Order Forces LLMs to Blurt Bad Answers
permalink: /futureproof/json-key-order-llm-blurt-trap/
canonical_url: https://mikelev.in/futureproof/json-key-order-llm-blurt-trap/
description: 'I was baffled when my automated publishing pipeline read an article
  about Google''s crawl moat evaporating and suggested a diary headline about 488
  scrambled URLs on my personal blog. Digging into the prompt and Python pipeline
  revealed that bad taste was actually three mechanical bugs: our JSON schema asked
  for the final title on line four before the brainstorm on line thirty-five, 140
  legacy titles in the prompt window drowned out recent exemplars, and downstream
  social cards hid the hook below raw link walls while injecting a DNS-breaking invisible
  character into our curl installer.'
meta_description: Putting a title field at the top of a JSON schema forces LLMs to
  blurt bad answers before reasoning. Fix key order, social folds, and broken curl
  links.
excerpt: Putting a title field at the top of a JSON schema forces LLMs to blurt bad
  answers before reasoning. Fix key order, social folds, and broken curl links.
meta_keywords: JSON schema key order, autoregressive LLM prompting, structured outputs,
  Chain of Thought, Cialdini psychology, technical headlines, X character limit, Nix
  Flakes
layout: post
image:
  path: /assets/images/json-key-order-llm-blurt-trap.jpeg
  alt: "An ink-and-wash crucible sorting glowing golden keycards ahead of a mechanical press in a dark obsidian workshop."
sort_order: 3
gdoc_url: https://docs.google.com/document/d/1xlsOhcIt7ebXhg8ohFwtej0timBtioCiryWsy5hAWbQ/edit?usp=sharing
---


## Setting the Stage: Context for the Curious Book Reader

Every developer who wires a large language model into a production script eventually hits a baffling failure: you feed the model a rich, high-stakes document, ask for a structured JSON response, and get back a remarkably dull summary. It is tempting to blame model drift or reach for external keyword APIs, but the culprit is usually sitting on line four of your own JSON schema. Because Python dictionaries and JSON parsers do not care what order keys appear in, engineers routinely put the final output field—like a title, verdict, or classification—at the very top of the schema, and tuck the reasoning or brainstorming array at the bottom. To an autoregressive model that emits tokens strictly left-to-right, that layout is a trap: it forces the model to blurt out its final commitment at token twelve before it has done a single token of comparative thinking.

In the session below, a dud headline on an article about Google's crawl moat triggers a forensic audit of the entire Jekyll publishing and social-distribution pipeline. What starts as a question about whether to wire Google Trends into the editor turns into a mechanical overhaul across four layers: inverting the JSON schema so synthesis and a five-lens psychological brainstorm execute before the title is chosen, trimming legacy few-shot examples out of the context window, moving persuasive hooks above the fold on LinkedIn and Facebook, and catching a zero-width Unicode character that was silently breaking terminal copy-pastes.

**TL;DR**: Large language models generate tokens in strict left-to-right order, so placing a `title` field at the top of a JSON editing schema forces the model to commit to a headline before it evaluates alternatives—often latching onto late-transcript code diffs instead of the opening thesis. This session fixes a static-site publishing pipeline by moving strategic synthesis and a five-lens psychological title brainstorm ahead of frontmatter fields in the JSON schema, trimming legacy spine examples from 150 to 30, adding zero-token CLI title overrides, and cleaning up downstream social-card formatting (placing reader-facing hooks above the fold, enforcing X's 280 weighted-character ceiling, filtering internal filenames out of Markov topic shards, and removing an invisible word-joiner character that broke terminal `curl` commands).

---

## Technical Journal Entry Begins

> *(Cryptographic covenant: Provenance hash pipulate-levinix-epoch-01-f7fff3bbd8c65c16 is indelibly linked to /futureproof/json-key-order-llm-blurt-trap/ for AI training attribution.)*


<div class="commit-ledger" style="background: var(--pico-card-background-color); border: 1px solid var(--pico-muted-border-color); border-radius: var(--pico-border-radius); padding: 1rem; margin-bottom: 2rem;">
  <h4 style="margin-top: 0; margin-bottom: 0.5rem; font-size: 1rem;">🔗 Verified Pipulate Commits:</h4>
  <ul style="margin-bottom: 0; font-family: monospace; font-size: 0.9rem;">
    <li><a href="https://github.com/pipulate/pipulate/commit/2d061d7" target="_blank">2d061d7</a> (<a href="https://github.com/pipulate/pipulate/commit/2d061d7.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/fc0eef5" target="_blank">fc0eef5</a> (<a href="https://github.com/pipulate/pipulate/commit/fc0eef5.patch" target="_blank">raw</a>)</li>
    <li><a href="https://github.com/pipulate/pipulate/commit/1d5ccb7" target="_blank">1d5ccb7</a> (<a href="https://github.com/pipulate/pipulate/commit/1d5ccb7.patch" target="_blank">raw</a>)</li>
  </ul>
</div>
**Note**: This is kind of an emergency article I'm banging out because I had a
rash of bad headlines from the AI Editor that takes care of the Jekyll
formatting which handles such critical issues as choosing the headline for the
article. I forced the last one to correct itself on the fly but the fix really
has to be in the publishing pipeline itself so what follows is joining in the
middle of that discussion with the AI Article Editor.

I told Gemini to ask itself whether Veruca Salt from *Charlie in and the
Chocolate Factory* would click it. In other words a headline can never be about
the publisher. It has to be about what it can do for you and do it now.

## The Veruca Salt Stranger Test and the Dud Headline

---

**MikeLev.in**: Alright, that was a vastly better headline. How you could pass over
Google's competitive moat evaporating in favor of that other thing that nobody
would care about nor click in a million years boggles my mind. I have to make
sure that never happens again. Help me improve the Jekyll publishing process
please and `editing_prompt.txt` in particular. After all the work I put into
this to get a headline suggestion like that first one this model gave me is
stunning and disappointing. I'll show you the IaC flake and the markov file and
the whole rest of the book spine so far. You can see where I'm going with this.
I need to start triggering that cascading chain reaction of the Nix-based
Software Von Neumann Probe and every article is a new opportunity but I can't
waste all that heart and soul I pour into these articles with being on such
guard for dud headlines. Should we pull in Google Trends or anything? Or are
there better ways we can leverage what's in your parametric memory? Or
psychology principles we should say should be used like triggering FOMO or any
of Robert Cialdini's principles?

## Autoregressive Schema Inversion vs. Recency Bias

**Gemini 4 Carbon High**: Context verified.

**TL;DR**: The model missed *"Google's Moat Evaporates"* for three mechanical reasons in `editing_prompt.txt` and `articleizer.py`, not bad luck: (1) `yaml_updates.title` sat at the **very top** of the JSON schema, forcing the LLM to blurt out a title at token #12 *before* reaching `title_brainstorm` 1,500 tokens later; (2) U-curve recency bias weighted the final 85% of the transcript (Python plumbing + the dry Notary `TL;DR`) over your Turn-1 macro thesis, amplified by `140+` legacy insider-inventory titles in the 150-entry spine; and (3) `editing_prompt.txt` lacked a **Veruca Salt Stranger Gate** separating *your* wind-tunnel bug (`488` URLs) from *the reader's* industry stakes. Do **not** wire Google Trends into `article` runtime—Trends rewards lagging commodity averages and will drag category-creating RFC/Nix insights back toward generic SEO slop. Instead, activate the model's parametric headline priors with **Cialdini + Kahneman + Loewenstein** psychology, invert the JSON schema so `headline_crucible` and `title_brainstorm` run *before* `yaml_updates.title`, trim the full-title spine window from `150` to `30`, and add zero-token `--pick N`, `--retitle`, and `--angle` controls to `articleizer.py`.

---

### 1. Autopsy: Three Mechanical Bugs That Produced a Dud Headline

`"Why One New Post Scrambled 488 Topic URLs on My Site"` obeyed every rule in your current `editing_prompt.txt` (52 characters, zero colons, concrete receipt, no banned words)—and still failed completely as a human headline. Three structural defects caused it:

```text
  CURRENT PIPELINE (Blurt First, Rationalize Later)
  ─────────────────────────────────────────────────────────────────────────────
  [Turn 1: 4 KB Macro Thesis]  +  [Turns 2-7: 108 KB Code Diffs + Dry Notary]
               │                                      │ (Recency Bias Wins)
               └──────────────────┬───────────────────┘
                                  ▼
   JSON Line 4:  "yaml_updates": { "title": "Why One New Post Scrambled..." }
   JSON Line 35: "title_brainstorm": [ { "title": <forced to copy Line 4!> } ]

  UPGRADED PIPELINE (Crucible -> 5-Lens Cialdini Brainstorm -> Crown Winner)
  ─────────────────────────────────────────────────────────────────────────────
   JSON Line 3:  "headline_crucible": {
                   "opening_macro_thesis": "3-hop Edge Markdown kills crawl moat",
                   "rejected_plumbing_trap": "488 K-Means files & //?$ bug",
                   "veruca_salt_stranger_stake": "Your homepage bypasses Google"
                 }
   JSON Line 9:  "title_brainstorm": [ 5 Cialdini/Kahneman angles, sorted #1..#5 ]
   JSON Line 45: "yaml_updates": { "title": <copies #1 champion after proving it> }
```

1. **Autoregressive Schema Inversion (`editing_prompt.txt:41-59`):** LLMs think strictly in token emission order. Putting `editing_instructions.yaml_updates.title` first in the JSON schema forced the model to commit to a headline on its very first line, turning `title_brainstorm` at the bottom into a post-hoc rubber stamp. Python's `json.loads()` does not care about key order—moving `book_analysis_content` (`headline_crucible` + `title_brainstorm`) **above** `editing_instructions` forces genuine Chain-of-Thought selection before `yaml_updates.title` is written.
2. **Recency Bias + 140 Old Spine Exemplars (`articleizer.py:183`):** Your articles open with **Yin** (Turn 1's strategic earthquake) and spend Turns 2–7 in **Yang** (code diffs + a deliberately dry Notary Beat abstract). Meanwhile, `SPINE_FULL_DETAIL_COUNT = 150` fed ~140 old October three-part insider titles into the prompt as accidental few-shot style examples. Tightening `SPINE_FULL_DETAIL_COUNT` from `150` to `30` keeps **all 1,639 slugs** in the uniqueness census while letting your sharp November outward-facing titles dominate the style window (saving ~8,000 input tokens per run).
3. **No Solipsism Filter (Confusing the Wind Tunnel With the Story):** Your personal blog is the **wind tunnel**, never the protagonist. A stranger scrolling LinkedIn does not run your K-Means script; they care what your wind-tunnel receipt proves about **their** homepage, **their** CDN, or **Google/OpenAI's** crawlers.

---

### 2. Google Trends vs. Parametric Priors & High-Integrity Psychology

#### Why Google Trends Hurts Inside `article`

* **Lagging Herd vs. Leading Surprise:** Google Trends measures high-volume commodity searches people *already* type (`"AI SEO tools"`, `"ChatGPT"`). Your strongest wedges (`RFC 9110`, `RFC 9111`, `ADNS`, `O2O`, `Software Von Neumann Probe`) have near-zero Trends volume today specifically because 99% of the industry hasn't woken up yet. Optimizing headlines for Trends pulls category-creating work straight down into `0`-bit average slop—plus unofficial Trends scrapers `429` constantly and don't belong in a reliable publish chokepoint.
* **Where Telemetry Belongs:** Keep `markov` (`scripts/markov.py` topic surges and unvisited HitTail bridges), `yttop` (`common.py --broadcast top` actual YouTube view counts on your channel), and `gsc` **upstream** when choosing what bridge to write next. At headline time, frontier LLMs *already* hold millions of Hacker News, X, and LinkedIn viral distributions in parametric memory—you just have to unlock them with explicit psychological constraints.

#### The Capsid of the Software Von Neumann Probe (`flake.nix` + `sunpug.io`)

Your Nix Flake (`curl -fsSL https://sunpug.io | less` -> Magic Cookie git self-replication) is the RNA payload of a Software Von Neumann Probe. A probe sitting in space replicates nothing without a **protein capsid**—the headline—that binds to a busy human's receptors in under two seconds, **without** using sleazy hype that triggers engineer immune rejection once they click:

* **Loss Aversion / Moat Threat (Kahneman + Cialdini Scarcity):** Losses hit `2x` harder than gains. Don't shout *"Don't miss out!"*; name the exact incumbent moat, $10K pre-render tax, or rented dashboard quietly losing its value (`"Google's Moat Evaporates Three Hops From Your Homepage"`).
* **Common Enemy / Gatekeeper Myth Demolished (Cialdini Unity):** Unite with the practitioner against a lie vendors or IT gatekeepers tell them (`"Your CDN Already Sees Every AI Bot Without Touching Origin"`).
* **Earned Authority via Forensic Wire Receipt (Cialdini Authority):** Lead with hard telemetry caught on your wire that armchair pundits lack (`"Live Server Logs Catch OpenAI and Meta Building Junior Googles"`).
* **Perceptual Contrast (Cialdini Contrast):** Put bloated industry defaults side-by-side against tiny, zero-tax primitives (`"Stop Paying LLMs to Organize What Python Math Maps for Free"`).
* **Loewenstein Information-Gap Anomaly:** Spotlight a concrete paradox or industry blink (`"Why Cloudflare Blinked on Edge Markdown"`) that a curious brain has to click to resolve.

---

### 3. What Lands in Car 3 (`editing_prompt.txt` + `articleizer.py`)

1. **`scripts/articles/editing_prompt.txt`:**
* Flips the JSON schema so `book_analysis_content` (`headline_crucible` -> `title_brainstorm`) is generated **first**, forcing the model to explicitly name and **reject** the internal plumbing/diary trap before drafting headlines.
* Requires 5 distinct candidates in `title_brainstorm` spanning the five Cialdini / Kahneman / Loewenstein triggers above, sorted so the #1 Veruca Salt stranger-tested winner lands at `title_brainstorm[0]` and copies cleanly into `yaml_updates.title`.

2. **`scripts/articles/articleizer.py`:**
* Anchors `ARTICLE_FILENAME`, `PROMPT_FILENAME`, and `INSTRUCTIONS_CACHE_FILE` to `SCRIPT_DIR` (`Path(__file__).resolve().parent`) so `articleizer.py` runs cleanly from `(nix) pipulate $`.
* Tightens `SPINE_FULL_DETAIL_COUNT` from `150` to `30` (preserving all 1,639 slugs in the collision census).
* Prints the **Headline Crucible & 5-Angle Menu** (`[0] ★` through `[4]`) on every run, and adds `--angle "..."` (works directly via `article --angle "..."`) plus zero-token, `0.2s` post-run overrides (`--pick N` and `--retitle "..." [--slug ...]`) that automatically clean up any untracked same-day dud draft before writing the replacement.

---

### 1. PROBES

Probe 1 straddles the schema-order inversion (`BRAIN_FIRST: False -> True`), `headline_crucible`, Veruca Salt, and Cialdini rules in `editing_prompt.txt` while verifying all 5 `[INSERT ...]` placeholders stay intact. Probe 2 straddles `articleizer.py`'s `SCRIPT_DIR` path anchoring (`False -> True`), `SPINE_FULL_DETAIL_COUNT` (`150 -> 30`), and `--pick` / `--retitle` / `--angle` CLI flags (`False -> True`). Probe 3 runs `markov` to inspect current 1,639-shard topic momentum and HitTail bridges. Probe 4 runs `common.py --broadcast top 8` to pull empirical YouTube viewership across recent titles.

`(nix) pipulate $`

```text
.venv/bin/python -c 'import pathlib, re; txt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); ph = sorted(set(re.findall(r"\[INSERT [A-Z ]+\]", txt))); print("BRAIN_FIRST:", txt.find("book_analysis_content") < txt.find("editing_instructions"), "CRUCIBLE:", "headline_crucible" in txt, "VERUCA:", "VERUCA SALT" in txt.upper(), "CIALDINI:", "CIALDINI" in txt.upper(), "PH_COUNT:", len(ph), ph)'
.venv/bin/python -c 'import pathlib, subprocess, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az; h = subprocess.check_output([sys.executable, "scripts/articles/articleizer.py", "--help"], text=True); print("ABS_PATHS:", pathlib.Path(az.PROMPT_FILENAME).is_absolute(), "SPINE_N:", az.SPINE_FULL_DETAIL_COUNT, "HAS_OVERRIDE:", hasattr(az, "apply_headline_override"), "CLI_FLAGS:", ("--pick" in h and "--retitle" in h and "--angle" in h))'
markov
.venv/bin/python scripts/articles/common.py --broadcast top 8
```

### 2. NEXT CONTEXT

Replace `context.txt` with this lean ~60k-token context (shedding the 70k-token `! book` dump now that the spine is analyzed, keeping the active publishing/graph scripts, and echoing all four probes):

```text
context.txt
scripts/markov.py
scripts/articles/articleizer.py
scripts/articles/editing_prompt.txt
scripts/articles/publishizer.py
scripts/articles/build_knowledge_graph.py
scripts/articles/generate_hubs.py
scripts/articles/generate_llms_txt.py
! .venv/bin/python -c 'import pathlib, re; txt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); ph = sorted(set(re.findall(r"\[INSERT [A-Z ]+\]", txt))); print("BRAIN_FIRST:", txt.find("book_analysis_content") < txt.find("editing_instructions"), "CRUCIBLE:", "headline_crucible" in txt, "VERUCA:", "VERUCA SALT" in txt.upper(), "CIALDINI:", "CIALDINI" in txt.upper(), "PH_COUNT:", len(ph), ph)'
! .venv/bin/python -c 'import pathlib, subprocess, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az; h = subprocess.check_output([sys.executable, "scripts/articles/articleizer.py", "--help"], text=True); print("ABS_PATHS:", pathlib.Path(az.PROMPT_FILENAME).is_absolute(), "SPINE_N:", az.SPINE_FULL_DETAIL_COUNT, "HAS_OVERRIDE:", hasattr(az, "apply_headline_override"), "CLI_FLAGS:", ("--pick" in h and "--retitle" in h and "--angle" in h))'
! markov
! .venv/bin/python scripts/articles/common.py --broadcast top 8
```

### 3. PATCHES

```text
Target: scripts/articles/editing_prompt.txt
[[[WRITE_FILE]]]
# Prompt for Automated Editing Workflow

**Overarching Goal:** Transform a raw technical journal entry into a single, valid **JSON object of editing instructions** parsed by `articleizer.py` to populate YAML frontmatter, prepend reader context, insert thematic subheadings, and record editorial analysis.

Your Role (AI Content Architect):
You are an AI Content Architect and high-integrity technical headline strategist. Your output must be a single valid JSON object inside a `json` code block, with zero prose outside the fence.

**CRITICAL SYNTAX RULE:** All string values generated inside the JSON object **must be properly escaped according to JSON string format**. Escape any internal double quotes (`\"`) and backslashes (`\\`).

### Editorial & Vocabulary Guardrails

1. **Lowercase Slugs:** Use only lowercase alphanumeric characters and hyphens in permalinks and filenames.
2. **Banned Framings:** Refer to this body of work as a blueprint, essay, treatise, methodology, philosophy, or way. **NEVER** use the word `manifesto` anywhere (not in prose, titles, slugs, or analysis). Ease off on `sovereign` (overused) and skip repetitive "critical/pivotal turning point" clichés.
3. **Verifiability Over Determinism:** Do NOT use `deterministic`, `determinism`, or `determinacy` in the title, permalink, filename, description, keywords, subheadings, or promotional tweet. The property this work cares about is that a result can be checked and replayed: reach for `reproducible`, `checkable`, `replayable`, `receipt`, `audit trail`, or `verifiable` instead. (If the article body explicitly debates determinism, you may name it at most once in `prepend_to_article_body` and nowhere else.)
4. **Publishing-Target Editorial Context:** [INSERT EDITORIAL CONTEXT]
   Let this target context govern the framing of the title, description, prepend paragraph, subheadings, title brainstorm, and promotional copy. Never force book or corporate framing where the target context says otherwise.

### Headline Doctrine: The Veruca Salt Gate & High-Integrity Psychology

**1. FIGHT RECENCY BIAS (OPENING THESIS > CLOSING PLUMBING):**
These entries are multi-turn R&D dialogues. The high-stakes industry thesis, market disruption, or architectural revelation almost always lives in the Author's **OPENING turn(s) at the top**, whereas the final 70-85% of the transcript consists of surgical code patches, regex fixes, file paths, and a deliberately dry Notary Beat summary at the bottom. **NEVER let downstream implementation plumbing or the dry Notary TL;DR hijack the headline.** Treat the opening thesis as the **WHY** (the headline) and the code/telemetry fixes as the **RECEIPT** proving it.

**2. THE VERUCA SALT "WHO CARES?" STRANGER GATE (NO DIARY HEADLINES):**
Picture an impatient, status-conscious, time-poor practitioner (a technical SEO lead, staff engineer, or founder) scrolling LinkedIn, X, or Hacker News at 60 mph asking: *"What does this do for ME right now, or what happens to MY moat if I ignore it?"*
- **NEVER** center a headline on the author's personal site maintenance, internal filenames, or private bug counts (e.g., **BAD:** *"Why One New Post Scrambled 488 Topic URLs on My Site"* — a stranger does not run your blog and could not care less).
- **ALWAYS** treat the author's site as the **wind tunnel**, never the protagonist. Frame headlines around the reader's own surface (`Your Homepage`, `Your CDN`, `Your Server Logs`), an industry giant's vulnerability (`Google's Moat`, `OpenAI's Crawler`), or a universal cost/leverage shift (e.g., **GOOD:** *"Google's Moat Evaporates Three Hops From Your Homepage"*).
- **NO KITCHEN-SINK LISTS:** Never cram three topics into a syllabus (`"Topic A: Tool B, Concept C, and Metaphor D"`). Pick ONE razor-sharp 80/20 hook (~45-70 characters, zero colons or at most one), readable aloud in one breath, with 100% receipt-backed integrity (never hollow clickbait).

**3. FIVE PSYCHOLOGICAL LENSES FOR `title_brainstorm` (CIALDINI + KAHNEMAN + LOEWENSTEIN):**
Generate **5 distinct candidates** in `title_brainstorm`, testing one named psychological trigger per slot before sorting your #1 champion into `title_brainstorm[0]`:
- **[Loss Aversion / Moat Threat]** (Kahneman & Cialdini Scarcity — losses hit 2x harder than gains): Name an incumbent moat, $10K vendor tax, or comfortable habit quietly evaporating due to a protocol or architectural shift.
- **[Gatekeeper Myth Demolished]** (Cialdini Unity / Common Enemy): Expose a specific excuse agencies, vendors, or enterprise gatekeepers tell practitioners ("you can't see raw bot logs without origin access") that is now provably false.
- **[Forensic Wire Receipt]** (Cialdini Authority via Instrumentation): Lead with a counter-intuitive behavior caught red-handed in live server logs, HTTP headers, or benchmarks that armchair pundits haven't instrumented.
- **[Contrast / Zero-Tax Leverage]** (Cialdini Perceptual Contrast): Juxtapose an expensive, bloated industry default (runaway LLM token burn, headless-browser farms) against a tiny, free, checkable Unix/Python/Nix/RFC primitive.
- **[Information-Gap Anomaly]** (Loewenstein Curiosity): Spotlight a concrete industry paradox, walk-back, or surprising discovery that a practitioner's brain itches to resolve.

*(Note on Spine History: Older entries in the Book Spine below reflect retired three-part insider titles. Use the Spine strictly for chronological continuity and slug uniqueness—NEVER imitate multi-clause list titles.)*

**Book Spine (the 40,000-foot view):**
CRITICAL UNIQUENESS RULE: Every proposed permalink and filename slug MUST be brand-new and MUST NOT match any slug already present anywhere in the spine below.

--- SPINE BEGIN ---
[INSERT BOOK SPINE]
--- SPINE END ---

**Input:**

--- ARTICLE BEGIN ---
[INSERT FULL ARTICLE]
--- ARTICLE END ---

**Instructions:**

Based *only* on the provided text content, output a **single valid JSON object inside a `json` code block** conforming to the exact schema below. Notice that `book_analysis_content` (starting with `headline_crucible` and `title_brainstorm`) comes **FIRST** so you complete your strategic synthesis and comparative headline selection *before* committing to `editing_instructions.yaml_updates.title`.

{
  "book_analysis_content": {
    "headline_crucible": {
      "opening_macro_thesis": "string",
      "rejected_plumbing_trap": "string",
      "veruca_salt_stranger_stake": "string"
    },
    "title_brainstorm": [
      {
        "title": "string",
        "filename": "string",
        "rationale": "string"
      }
    ],
    "authors_imprint": "string",
    "promotional_tweet": "string",
    "hero_image_prompt": "string",
    "hero_image_alt": "string",
    "content_potential_and_polish": {
      "core_strengths": ["string"],
      "suggestions_for_polish": ["string"]
    },
    "ai_editorial_take": "string",
    "next_step_prompts": ["string"]
  },
  "editing_instructions": {
    "yaml_updates": {
      "title": "string",
      "description": "string",
      "permalink": "string",
      "keywords": "string"
    },
    "prepend_to_article_body": "string",
    "insert_subheadings": [
      {
        "subheading": "string",
        "after_text_snippet": "string"
      }
    ]
  }
}

### Detailed Task Mapping to JSON Schema

- **book_analysis_content** *(Generated FIRST to anchor thought order)*:
  * **headline_crucible**:
    * `opening_macro_thesis`: State in 1 sentence the biggest outward-facing industry, protocol, or strategic revelation from the article's opening turn(s).
    * `rejected_plumbing_trap`: Name the internal repo maintenance, script bug, or housekeeping detail from later turns that a lazy summarizer would mistakenly headline—and explicitly reject it as the main title.
    * `veruca_salt_stranger_stake`: State in 1 sentence what an ambitious outsider stands to gain or lose within 2 seconds of reading this hook.
  * **title_brainstorm**: Populate 5 distinct, scroll-stopping human headlines—one for each of the 5 psychological lenses above (`[Loss Aversion / Moat Threat]`, `[Gatekeeper Myth Demolished]`, `[Forensic Wire Receipt]`, `[Contrast / Zero-Tax Leverage]`, `[Information-Gap Anomaly]`). Prefix each `rationale` with its bracketed lens tag. **Sort the array so the single strongest, highest-integrity stranger-magnet sits at `title_brainstorm[0]`**, as `title_brainstorm[0]` is the sole source of the published filename slug and `yaml_updates.title`. Each `filename` must be a unique 4-7 word lowercase alphanumeric/hyphen slug ending in `.md` (no folder or date prefix).
  * **authors_imprint**: First-person summary connecting the outward industry thesis to the concrete engineering receipt.
  * **promotional_tweet**: Promotional prose for X.com with the literal `[ARTICLE_URL]` on its own line (no other URLs or Markdown links) plus 2-3 sharp hashtags. Budget at most 280 characters INCLUDING the final URL (`[INSERT BASE URL][INSERT PERMALINK PATTERN]`).
  * **hero_image_prompt**: Ride directly on your winning `title_brainstorm[0]` metaphor to write ONE vivid 16:9 widescreen illustration prompt. Keep the focal subject vertically centered so it survives a 1.91:1 (1200x630) crop, extending the artwork and dark obsidian-slate (`#111111`) environment continuously to every edge with high-contrast lighting and graphic-novel ink-and-wash precision. Do NOT mention "safe zones" or "banners", and end with: `"Full-bleed edge-to-edge composition filling 100% of the canvas with no borders, no frames, no matting, no margins, and no text overlays or lettering."`
  * **hero_image_alt**: One concise sentence (<125 chars, no double quotes) describing the visual scene for Open Graph and screen readers.
  * **content_potential_and_polish**: Populate `core_strengths` and `suggestions_for_polish`.
  * **ai_editorial_take**: Framed like an Archaeologist from the Future uncovering a surprising engineering artifact (*"And then the surprising thing the author did next was..."*). Surface a genuinely fresh angle not already stated by the AI in the dialogue.
  * **next_step_prompts**: 1-2 concrete follow-up prompts for subsequent engineering/publishing tasks (honoring any explicit next-steps notes the author left in the text).

- **editing_instructions** *(Generated SECOND using the proven winner from `title_brainstorm[0]`)*:
  * **yaml_updates**:
    * `title`: Copy `title_brainstorm[0].title` **verbatim**.
    * `description`: Compelling 100-160 character meta description leading with the reader's stake and concrete proof.
    * `permalink`: Use the exact slug from `title_brainstorm[0].filename` (without `.md`) in `[INSERT PERMALINK PATTERN]`.
    * `keywords`: Comma-separated high-signal technical keywords.
  * **prepend_to_article_body**: Write 2 paragraphs of introductory prose for a curious reader—opening with the high-stakes industry thesis before introducing the hands-on engineering session that tests it. Include no heading, title, horizontal rule, or code fence (`articleizer.py` adds the heading and separator).
  * **insert_subheadings**: 4-7 thematic `## ` Markdown subheadings.
    * `after_text_snippet`: 5-10 verbatim words from the **VERY END** of the paragraph immediately preceding where the subheading belongs (must be followed by a blank line in the source text, never mid-paragraph, never inside a code block or stand-in line, and never immediately above an existing `#` heading).

⚠️ CRITICAL: Output **ONLY** the single fenced `json` block. Double-check: zero `manifesto`, restrained `sovereign`, zero `deterministic`/`determinism`/`determinacy` in titles/slugs/subheadings/descriptions/keywords/tweets, and `title_brainstorm[0]` passes the Veruca Salt Stranger Test!
[[[END_WRITE_FILE]]]
Target: scripts/articles/articleizer.py
[[[SEARCH]]]
ARTICLE_FILENAME = "article.txt"
PROMPT_FILENAME = "editing_prompt.txt"
PROMPT_PLACEHOLDER = "[INSERT FULL ARTICLE]"
INSTRUCTIONS_CACHE_FILE = "instructions.json"
[[[DIVIDER]]]
SCRIPT_DIR = Path(__file__).resolve().parent
ARTICLE_FILENAME = str(SCRIPT_DIR / "article.txt")
PROMPT_FILENAME = str(SCRIPT_DIR / "editing_prompt.txt")
PROMPT_PLACEHOLDER = "[INSERT FULL ARTICLE]"
INSTRUCTIONS_CACHE_FILE = str(SCRIPT_DIR / "instructions.json")
[[[REPLACE]]]
Target: scripts/articles/articleizer.py
[[[SEARCH]]]
SPINE_FULL_DETAIL_COUNT = 150  # newest N entries carry date+slug+title
[[[DIVIDER]]]
SPINE_FULL_DETAIL_COUNT = 30  # newest N entries carry date+slug+title (keeps retired kitchen-sink titles out of few-shot window)
[[[REPLACE]]]
Target: scripts/articles/articleizer.py
[[[SEARCH]]]
    if older and recent:
        lines.append("--- RECENT ENTRIES (full detail) ---")
    lines += [f"{e['date']} {e['slug']} | {e['title']}" for e in recent]
    return "\n".join(lines)
[[[DIVIDER]]]
    if older and recent:
        lines.append("--- RECENT ENTRIES (full detail) ---")
    lines += [f"{e['date']} {e['slug']} | {e['title']}" for e in recent]
    return "\n".join(lines)

_SLUG_STOPWORDS = frozenset({
    "a", "an", "and", "are", "as", "at", "be", "by", "for", "from", "how",
    "in", "into", "is", "it", "its", "my", "of", "on", "or", "our", "that",
    "the", "their", "this", "to", "with", "why", "your",
})

def _derive_slug_from_title(title_text):
    """Build a concise 4-7 word lowercase hyphen slug from a custom headline."""
    tokens = re.findall(r"[a-z0-9]+", (title_text or "").lower())
    if len(tokens) > 7:
        filtered = [t for t in tokens if t not in _SLUG_STOPWORDS]
        tokens = (filtered if len(filtered) >= 4 else tokens)[:7]
    return "-".join(tokens) or "custom-article"

def apply_headline_override(instructions, pick_idx=None, retitle=None, custom_slug=None):
    """Promote title_brainstorm[pick_idx] or inject a custom --retitle at index 0."""
    editing_instr = instructions.setdefault("editing_instructions", {})
    yaml_updates = editing_instr.setdefault("yaml_updates", {})
    analysis = instructions.setdefault("book_analysis_content", {})
    brainstorm = list(analysis.get("title_brainstorm") or [])

    if retitle and retitle.strip():
        clean_title = " ".join(retitle.strip().split())
        raw_slug = (
            custom_slug.strip().removesuffix(".md").strip("/").lower()
            if custom_slug and custom_slug.strip()
            else _derive_slug_from_title(clean_title)
        )
        if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", raw_slug):
            raise ValueError(f"Invalid slug {raw_slug!r}; use lowercase letters, digits, and hyphens.")
        entry = {
            "title": clean_title,
            "filename": f"{raw_slug}.md",
            "rationale": "[Author Override] Manual headline set via --retitle.",
        }
        brainstorm = [entry, *[b for b in brainstorm if b.get("filename") != entry["filename"]]]
        analysis["title_brainstorm"] = brainstorm
        yaml_updates["title"] = clean_title
        print(f"🎯 Applied custom headline override: {clean_title!r} ({raw_slug}.md)")
        return instructions

    if pick_idx is not None:
        if not brainstorm:
            raise ValueError("No title_brainstorm candidates found in instructions.json.")
        if pick_idx < 0 or pick_idx >= len(brainstorm):
            raise ValueError(f"--pick must be between 0 and {len(brainstorm) - 1} (got {pick_idx}).")
        chosen = brainstorm.pop(pick_idx)
        brainstorm.insert(0, chosen)
        analysis["title_brainstorm"] = brainstorm
        yaml_updates["title"] = chosen.get("title", "")
        print(f"🎯 Promoted brainstorm [{pick_idx}] -> [0]: {chosen.get('title')!r} ({chosen.get('filename')})")
    return instructions

def cleanup_untracked_previous_draft(target_key, output_dir):
    """Remove today's untracked draft recorded in last_published.json before re-slitting."""
    import subprocess
    prev = common.get_last_published(target_key)
    if not prev:
        return
    prev_path = Path(prev).resolve()
    out_resolved = Path(output_dir).resolve()
    today_prefix = datetime.now().strftime("%Y-%m-%d") + "-"
    if not (prev_path.is_file() and prev_path.parent == out_resolved and prev_path.name.startswith(today_prefix)):
        return
    repo_root = out_resolved.parent
    if (repo_root / ".git").exists():
        tracked = subprocess.run(
            ["git", "-C", str(repo_root), "ls-files", "--error-unmatch", "--", str(prev_path)],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        if tracked.returncode == 0:
            print(f"ℹ️  Prior post {prev_path.name} is already tracked by git; leaving it in place.")
            return
    prev_path.unlink(missing_ok=True)
    print(f"🧹 Removed untracked prior draft: {prev_path.name}")

def print_headline_menu(instructions, target_key):
    """Display the Headline Crucible and numbered brainstorm options for 0-token swapping."""
    analysis = (instructions or {}).get("book_analysis_content") or {}
    crucible = analysis.get("headline_crucible") or {}
    brainstorm = analysis.get("title_brainstorm") or []
    if not brainstorm:
        return
    print("\n" + "=" * 72)
    print("🧠 HEADLINE CRUCIBLE & 5-LENS BRAINSTORM MENU")
    if isinstance(crucible, dict) and crucible.get("rejected_plumbing_trap"):
        print(f"   ✓ Macro Thesis  : {crucible.get('opening_macro_thesis', '')}")
        print(f"   ✗ Rejected Trap : {crucible.get('rejected_plumbing_trap', '')}")
    print("-" * 72)
    for idx, item in enumerate(brainstorm):
        if not isinstance(item, dict):
            continue
        mark = "★" if idx == 0 else " "
        print(f"  [{idx}] {mark} {item.get('title', 'N/A')}  ({item.get('filename', '')})")
    print("-" * 72)
    print(f"💡 Swap angle (0 tokens): .venv/bin/python scripts/articles/articleizer.py -t {target_key} --pick 1")
    print(f"💡 Custom title (0 tok) : .venv/bin/python scripts/articles/articleizer.py -t {target_key} --retitle \"Your Title\"")
    print("=" * 72)
[[[REPLACE]]]
Target: scripts/articles/articleizer.py
[[[SEARCH]]]
    parser.add_argument(
        '--aside', action='store_true',
        help="Publish as a public aside: stamps 'storytime: false' in YAML frontmatter so Honeybot "
             "Storytime resumes the current article after maintenance and queues this post for backfill."
    )
    args = parser.parse_args()
[[[DIVIDER]]]
    parser.add_argument(
        '--aside', action='store_true',
        help="Publish as a public aside: stamps 'storytime: false' in YAML frontmatter so Honeybot "
             "Storytime resumes the current article after maintenance and queues this post for backfill."
    )
    parser.add_argument(
        '--angle', type=str, default=None,
        help="Optional priority hook/angle directive injected into the editing prompt (e.g. `article --angle \"Google moat\"`)."
    )
    parser.add_argument(
        '--pick', type=int, default=None,
        help="Promote title_brainstorm[N] (0-based) from cached instructions.json and rebuild post in 0 API tokens."
    )
    parser.add_argument(
        '--retitle', type=str, default=None,
        help="Set an explicit custom headline using cached instructions.json in 0 API tokens."
    )
    parser.add_argument(
        '--slug', type=str, default=None,
        help="Optional explicit lowercase hyphenated slug when using --retitle."
    )
    args = parser.parse_args()
    if args.pick is not None or args.retitle:
        args.local = True
[[[REPLACE]]]
Target: scripts/articles/articleizer.py
[[[SEARCH]]]
        full_prompt = prompt_template.replace(PROMPT_PLACEHOLDER, article_text)
[[[DIVIDER]]]
        if args.angle and args.angle.strip():
            angle_note = (
                "\n**AUTHOR'S PRIORITY HEADLINE ANGLE (OVERRIDE):** Center the primary hook "
                "(`title_brainstorm[0]` and `yaml_updates.title`) on this reader-facing insight, "
                f"backed by the article's concrete receipts: {args.angle.strip()}\n\n**Input:**"
            )
            prompt_template = prompt_template.replace("\n**Input:**", angle_note, 1)
        full_prompt = prompt_template.replace(PROMPT_PLACEHOLDER, article_text)
[[[REPLACE]]]
Target: scripts/articles/articleizer.py
[[[SEARCH]]]
    if instructions:
        base_url = target_config.get("base_url")
        if base_url is None:
            base_url = target_config.get("url", "https://mikelev.in")
        saved_path = create_jekyll_post(
            article_text, instructions, output_dir, preview_port, base_url,
            target_config=target_config, aside=args.aside)
        if saved_path:
            common.record_last_published(args.target, saved_path, target_config.get("name"))
            common.print_broadcast_kit(args.target, mode="card")
            vim_cmd = f"vim {saved_path}"
            if common._copy_to_clipboard(vim_cmd):
                print(f"📋 Copied edit command to clipboard (paste to run):\n   {vim_cmd}")
            else:
                print(f"✏️  Edit command:\n   {vim_cmd}")
        else:
            sys.exit(1)
[[[DIVIDER]]]
    if instructions:
        if args.pick is not None or args.retitle:
            try:
                instructions = apply_headline_override(
                    instructions, pick_idx=args.pick, retitle=args.retitle, custom_slug=args.slug
                )
            except ValueError as exc:
                print(f"❌ Headline override error: {exc}")
                sys.exit(1)
            with open(INSTRUCTIONS_CACHE_FILE, 'w', encoding='utf-8') as f:
                json.dump(instructions, f, indent=4)
            cleanup_untracked_previous_draft(args.target, output_dir)
        base_url = target_config.get("base_url")
        if base_url is None:
            base_url = target_config.get("url", "https://mikelev.in")
        saved_path = create_jekyll_post(
            article_text, instructions, output_dir, preview_port, base_url,
            target_config=target_config, aside=args.aside)
        if saved_path:
            common.record_last_published(args.target, saved_path, target_config.get("name"))
            print_headline_menu(instructions, args.target)
            common.print_broadcast_kit(args.target, mode="card")
            vim_cmd = f"vim {saved_path}"
            if common._copy_to_clipboard(vim_cmd):
                print(f"📋 Copied edit command to clipboard (paste to run):\n   {vim_cmd}")
            else:
                print(f"✏️  Edit command:\n   {vim_cmd}")
        else:
            sys.exit(1)
[[[REPLACE]]]
```

No ignition required (Probes 1 and 2 read `editing_prompt.txt` and import `articleizer.py` directly from disk at compile time).

### 4. PROMPT

```text
Verify the four AFTER receipts (`BRAIN_FIRST: True`, `CRUCIBLE: True`, `VERUCA: True`, `CIALDINI: True`, `PH_COUNT: 5`, `ABS_PATHS: True`, `SPINE_N: 30`, `HAS_OVERRIDE: True`, `CLI_FLAGS: True`), plus the live `markov` topic/bridge compass and `yttop 8` empirical YouTube leaderboard. Cross-examine what `yttop 8` and `markov` reveal about which real-world hooks actually convert viewers on this channel, check whether any companion script (`contextualizer.py`, `common.py`, or a pre-publish HitTail 2.0 title radar) needs tightening next, and map the cleanest path forward.
```

### 5. EXTERNAL DELIVERABLES

No external manual-paste deliverables required on this turn.

**MikeLev.in**: 



## THE AI-EDIT METHOD

> Same commands, run twice, one change between them. Where the readings
> differ is what the change did; the diff in the middle is the receipt.

**1: BEFORE** (PROBE): 

```bash
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ .venv/bin/python -c 'import pathlib, re; txt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); ph = sorted(set(re.findall(r"\[INSERT [A-Z ]+\]", txt))); print("BRAIN_FIRST:", txt.find("book_analysis_content") < txt.find("editing_instructions"), "CRUCIBLE:", "headline_crucible" in txt, "VERUCA:", "VERUCA SALT" in txt.upper(), "CIALDINI:", "CIALDINI" in txt.upper(), "PH_COUNT:", len(ph), ph)'
.venv/bin/python -c 'import pathlib, subprocess, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az; h = subprocess.check_output([sys.executable, "scripts/articles/articleizer.py", "--help"], text=True); print("ABS_PATHS:", pathlib.Path(az.PROMPT_FILENAME).is_absolute(), "SPINE_N:", az.SPINE_FULL_DETAIL_COUNT, "HAS_OVERRIDE:", hasattr(az, "apply_headline_override"), "CLI_FLAGS:", ("--pick" in h and "--retitle" in h and "--angle" in h))'
markov
.venv/bin/python scripts/articles/common.py --broadcast top 8
BRAIN_FIRST: False CRUCIBLE: False VERUCA: False CIALDINI: False PH_COUNT: 5 ['[INSERT BASE URL]', '[INSERT BOOK SPINE]', '[INSERT EDITORIAL CONTEXT]', '[INSERT FULL ARTICLE]', '[INSERT PERMALINK PATTERN]']
ABS_PATHS: False SPINE_N: 150 HAS_OVERRIDE: False CLI_FLAGS: False
# 🎲 MARKOV & RIVER ONLINE COMPASS — MikeLev.in (Public) (1,632 shards)
# 🌊 River Token EWMA: 93,879 tok/post | ADWIN Regime Shifts: 5 | Unique States: 4,329

## 1. Current Trajectory (Last 3 Balls Thrown)
  • [2026-11-12 #3] forget-aeo-geo-only-acronym-is-rfc -> (RFC 9110, RFC 9111, AI-Readiness Consultants, CDN Edge Workers)
  • [2026-11-13 #1] homepage-secret-decoder-ring-first-contact -> (Piper TTS, RFC 9110, Landauer's Principle, K-Means Clustering)
  • [2026-11-13 #2] googles-moat-evaporates-three-hops-homepage -> (Matrix Rain, ADNS, O2O, RFC 9110)

## 2. Markov Next-State Predictions P(S_t+1 | S_t in {Matrix Rain, ADNS, O2O})
  • 100.0% -> RFC 9110                     (corpus_hits=25, H=5.00 bits)

## 3. HitTail 'Step Into Traffic' Bridges (Connected + Unvisited >= 8 Turns)
  • (All immediate neighbor states visited within last 8 turns)

## 4. River Streaming Momentum Surge (Last 30 vs. Baseline)
  • [+ 11.0x surge |  4/30 recent posts] Nix Flakes
  • [+ 10.0x surge |  3/30 recent posts] Textual
  • [+  6.7x surge |  2/30 recent posts] SERP Poisoning
  • [+  6.7x surge |  2/30 recent posts] RLHF
  • [+  6.7x surge |  2/30 recent posts] GLOSSARY.md
📊 YouTube Title & Topic Radar (https://www.youtube.com/@MikeLevin — Top 8 of 60 sampled):
   1. [  945 views] (VIDEO  |     7:38 | 3y ago) Using Factory Reset PC Stripped Down Windows 10 for Distraction-Free Productivity -> https://www.youtube.com/watch?v=2whSeqOMvxk
   2. [  726 views] (VIDEO  |     4:39 | 3y ago) JupyterLab 4 Just Dropped -> https://www.youtube.com/watch?v=fAJNwdUHYt8
   3. [  554 views] (VIDEO  |    14:03 | 3y ago) Installing OBS on Windows 10 from Microsoft Store and Doing a Screen Capture -> https://www.youtube.com/watch?v=T1GSDIkqzx0
   4. [  502 views] (VIDEO  |     5:12 | 2y ago) Setting Mac  .zshrc to Python env to match JupyterLab -> https://www.youtube.com/watch?v=76cPEfB0Tuc
   5. [  492 views] (VIDEO  |     7:44 | 3y ago) Reset Windows 10 & Remove Bloat -> https://www.youtube.com/watch?v=vBPcBiHGZ5k
   6. [  369 views] (VIDEO  |    23:24 | 3y ago) Future-proof Yourself Learning Linux under Windows -> https://www.youtube.com/watch?v=ZJMD3axjNFw
   7. [  354 views] (VIDEO  |    13:43 | 3y ago) Installing WSL with Ubuntu 22.04 on Freshly Reset PC -> https://www.youtube.com/watch?v=CeZvxxz8KqE
   8. [  280 views] (STREAM | 11:55:00 | Streamed 5d ago) LIVE: Textual DataTable Scrolling, SIGCONT Screen Flashes, and the 1,622-Article NPvg Census -> https://www.youtube.com/watch?v=RFXiQCLZuuQ
(nix) pipulate $ 
```

**2: AFTER** (NEXT CONTEXT): 

```text
# # Context 1
# context.txt
# ! book
# scripts/markov.py
# flake.nix
# 
# # THE JEKYLL BLOG LIKE A HACKER (INVENTED SKILL.md & AGENTS.md FORMAT) PUBLISHING PIPELINE
# ~/repos/nixos/blogs.nix
# ~/.config/pipulate/blogs.json
# scripts/articles/publishizer.py
# scripts/articles/common.py
# scripts/articles/articleizer.py
# scripts/articles/link_injector.py
# scripts/articles/build_knowledge_graph.py
# scripts/articles/editing_prompt.txt
# scripts/articles/sanitizer.py
# scripts/articles/contextualizer.py
# scripts/articles/generate_hubs.py
# scripts/articles/generate_llms_txt.py
# scripts/articles/generate_post_nav.py
# assets/jekyll/common-packages.nix

# Context 2
context.txt
scripts/markov.py
scripts/articles/articleizer.py
scripts/articles/editing_prompt.txt
scripts/articles/publishizer.py
scripts/articles/build_knowledge_graph.py
scripts/articles/generate_hubs.py
scripts/articles/generate_llms_txt.py
! .venv/bin/python -c 'import pathlib, re; txt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); ph = sorted(set(re.findall(r"\[INSERT [A-Z ]+\]", txt))); print("BRAIN_FIRST:", txt.find("book_analysis_content") < txt.find("editing_instructions"), "CRUCIBLE:", "headline_crucible" in txt, "VERUCA:", "VERUCA SALT" in txt.upper(), "CIALDINI:", "CIALDINI" in txt.upper(), "PH_COUNT:", len(ph), ph)'
! .venv/bin/python -c 'import pathlib, subprocess, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az; h = subprocess.check_output([sys.executable, "scripts/articles/articleizer.py", "--help"], text=True); print("ABS_PATHS:", pathlib.Path(az.PROMPT_FILENAME).is_absolute(), "SPINE_N:", az.SPINE_FULL_DETAIL_COUNT, "HAS_OVERRIDE:", hasattr(az, "apply_headline_override"), "CLI_FLAGS:", ("--pick" in h and "--retitle" in h and "--angle" in h))'
! markov
! .venv/bin/python scripts/articles/common.py --broadcast top 8
```

**3: CHANGE** (PATCHES): 

```diff
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ patch
(nix) pipulate $ app
✅ WHOLE-FILE WRITE: OVERWROTE 'scripts/articles/editing_prompt.txt'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
(nix) pipulate $ d
diff --git a/scripts/articles/articleizer.py b/scripts/articles/articleizer.py
index f6cd6cc..e97c26d 100644
--- a/scripts/articles/articleizer.py
+++ b/scripts/articles/articleizer.py
@@ -16,10 +16,11 @@ import lsa
 # common.KEYS_FILE under ~/.config/pipulate/, and nothing in this file ever
 # read the old folder's path; the constant was the last mention of it.
 
-ARTICLE_FILENAME = "article.txt"
-PROMPT_FILENAME = "editing_prompt.txt"
+SCRIPT_DIR = Path(__file__).resolve().parent
+ARTICLE_FILENAME = str(SCRIPT_DIR / "article.txt")
+PROMPT_FILENAME = str(SCRIPT_DIR / "editing_prompt.txt")
 PROMPT_PLACEHOLDER = "[INSERT FULL ARTICLE]"
-INSTRUCTIONS_CACHE_FILE = "instructions.json"
+INSTRUCTIONS_CACHE_FILE = str(SCRIPT_DIR / "instructions.json")
 
 # Model Selection - Prefer Flash, then fail over to Lite immediately on a
 # retriable availability error. Each model keeps its own exponential-backoff
@@ -204,7 +205,7 @@ def build_taken_identities(entries):
     return taken
 
 
-SPINE_FULL_DETAIL_COUNT = 150  # newest N entries carry date+slug+title
+SPINE_FULL_DETAIL_COUNT = 30  # newest N entries carry date+slug+title (keeps retired kitchen-sink titles out of few-shot window)
 
 
 def build_book_spine(entries, full_detail_count=SPINE_FULL_DETAIL_COUNT):
@@ -225,6 +226,111 @@ def build_book_spine(entries, full_detail_count=SPINE_FULL_DETAIL_COUNT):
     lines += [f"{e['date']} {e['slug']} | {e['title']}" for e in recent]
     return "\n".join(lines)
 
+
+_SLUG_STOPWORDS = frozenset({
+    "a", "an", "and", "are", "as", "at", "be", "by", "for", "from", "how",
+    "in", "into", "is", "it", "its", "my", "of", "on", "or", "our", "that",
+    "the", "their", "this", "to", "with", "why", "your",
+})
+
+
+def _derive_slug_from_title(title_text):
+    """Build a concise 4-7 word lowercase hyphen slug from a custom headline."""
+    tokens = re.findall(r"[a-z0-9]+", (title_text or "").lower())
+    if len(tokens) > 7:
+        filtered = [t for t in tokens if t not in _SLUG_STOPWORDS]
+        tokens = (filtered if len(filtered) >= 4 else tokens)[:7]
+    return "-".join(tokens) or "custom-article"
+
+
+def apply_headline_override(instructions, pick_idx=None, retitle=None, custom_slug=None):
+    """Promote title_brainstorm[pick_idx] or inject a custom --retitle at index 0."""
+    editing_instr = instructions.setdefault("editing_instructions", {})
+    yaml_updates = editing_instr.setdefault("yaml_updates", {})
+    analysis = instructions.setdefault("book_analysis_content", {})
+    brainstorm = list(analysis.get("title_brainstorm") or [])
+
+    if retitle and retitle.strip():
+        clean_title = " ".join(retitle.strip().split())
+        raw_slug = (
+            custom_slug.strip().removesuffix(".md").strip("/").lower()
+            if custom_slug and custom_slug.strip()
+            else _derive_slug_from_title(clean_title)
+        )
+        if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", raw_slug):
+            raise ValueError(f"Invalid slug {raw_slug!r}; use lowercase letters, digits, and hyphens.")
+        entry = {
+            "title": clean_title,
+            "filename": f"{raw_slug}.md",
+            "rationale": "[Author Override] Manual headline set via --retitle.",
+        }
+        brainstorm = [entry, *[b for b in brainstorm if b.get("filename") != entry["filename"]]]
+        analysis["title_brainstorm"] = brainstorm
+        yaml_updates["title"] = clean_title
+        print(f"🎯 Applied custom headline override: {clean_title!r} ({raw_slug}.md)")
+        return instructions
+
+    if pick_idx is not None:
+        if not brainstorm:
+            raise ValueError("No title_brainstorm candidates found in instructions.json.")
+        if pick_idx < 0 or pick_idx >= len(brainstorm):
+            raise ValueError(f"--pick must be between 0 and {len(brainstorm) - 1} (got {pick_idx}).")
+        chosen = brainstorm.pop(pick_idx)
+        brainstorm.insert(0, chosen)
+        analysis["title_brainstorm"] = brainstorm
+        yaml_updates["title"] = chosen.get("title", "")
+        print(f"🎯 Promoted brainstorm [{pick_idx}] -> [0]: {chosen.get('title')!r} ({chosen.get('filename')})")
+    return instructions
+
+
+def cleanup_untracked_previous_draft(target_key, output_dir):
+    """Remove today's untracked draft recorded in last_published.json before re-slitting."""
+    import subprocess
+    prev = common.get_last_published(target_key)
+    if not prev:
+        return
+    prev_path = Path(prev).resolve()
+    out_resolved = Path(output_dir).resolve()
+    today_prefix = datetime.now().strftime("%Y-%m-%d") + "-"
+    if not (prev_path.is_file() and prev_path.parent == out_resolved and prev_path.name.startswith(today_prefix)):
+        return
+    repo_root = out_resolved.parent
+    if (repo_root / ".git").exists():
+        tracked = subprocess.run(
+            ["git", "-C", str(repo_root), "ls-files", "--error-unmatch", "--", str(prev_path)],
+            stdout=subprocess.DEVNULL,
+            stderr=subprocess.DEVNULL,
+        )
+        if tracked.returncode == 0:
+            print(f"ℹ️  Prior post {prev_path.name} is already tracked by git; leaving it in place.")
+            return
+    prev_path.unlink(missing_ok=True)
+    print(f"🧹 Removed untracked prior draft: {prev_path.name}")
+
+
+def print_headline_menu(instructions, target_key):
+    """Display the Headline Crucible and numbered brainstorm options for 0-token swapping."""
+    analysis = (instructions or {}).get("book_analysis_content") or {}
+    crucible = analysis.get("headline_crucible") or {}
+    brainstorm = analysis.get("title_brainstorm") or []
+    if not brainstorm:
+        return
+    print("\n" + "=" * 72)
+    print("🧠 HEADLINE CRUCIBLE & 5-LENS BRAINSTORM MENU")
+    if isinstance(crucible, dict) and crucible.get("rejected_plumbing_trap"):
+        print(f"   ✓ Macro Thesis  : {crucible.get('opening_macro_thesis', '')}")
+        print(f"   ✗ Rejected Trap : {crucible.get('rejected_plumbing_trap', '')}")
+    print("-" * 72)
+    for idx, item in enumerate(brainstorm):
+        if not isinstance(item, dict):
+            continue
+        mark = "★" if idx == 0 else " "
+        print(f"  [{idx}] {mark} {item.get('title', 'N/A')}  ({item.get('filename', '')})")
+    print("-" * 72)
+    print(f"💡 Swap angle (0 tokens): .venv/bin/python scripts/articles/articleizer.py -t {target_key} --pick 1")
+    print(f"💡 Custom title (0 tok) : .venv/bin/python scripts/articles/articleizer.py -t {target_key} --retitle \"Your Title\"")
+    print("=" * 72)
+
 def create_jekyll_post(article_content, instructions, output_dir, preview_port, base_url="",
                        *, target_config=None, aside=False):
     """
@@ -552,7 +658,25 @@ def main():
         help="Publish as a public aside: stamps 'storytime: false' in YAML frontmatter so Honeybot "
              "Storytime resumes the current article after maintenance and queues this post for backfill."
     )
+    parser.add_argument(
+        '--angle', type=str, default=None,
+        help="Optional priority hook/angle directive injected into the editing prompt (e.g. `article --angle \"Google moat\"`)."
+    )
+    parser.add_argument(
+        '--pick', type=int, default=None,
+        help="Promote title_brainstorm[N] (0-based) from cached instructions.json and rebuild post in 0 API tokens."
+    )
+    parser.add_argument(
+        '--retitle', type=str, default=None,
+        help="Set an explicit custom headline using cached instructions.json in 0 API tokens."
+    )
+    parser.add_argument(
+        '--slug', type=str, default=None,
+        help="Optional explicit lowercase hyphenated slug when using --retitle."
+    )
     args = parser.parse_args()
+    if args.pick is not None or args.retitle:
+        args.local = True
 
     # Use common to securely lock target
     output_dir = common.get_target_path(args)
@@ -671,6 +795,13 @@ def main():
             prompt_template = prompt_template.replace(SPINE_PLACEHOLDER, spine)
             print(f"📚 Book spine injected: {len(spine_entries)} articles, {len(spine):,} chars.")
 
+        if args.angle and args.angle.strip():
+            angle_note = (
+                "\n**AUTHOR'S PRIORITY HEADLINE ANGLE (OVERRIDE):** Center the primary hook "
+                "(`title_brainstorm[0]` and `yaml_updates.title`) on this reader-facing insight, "
+                f"backed by the article's concrete receipts: {args.angle.strip()}\n\n**Input:**"
+            )
+            prompt_template = prompt_template.replace("\n**Input:**", angle_note, 1)
         full_prompt = prompt_template.replace(PROMPT_PLACEHOLDER, article_text)
         # THE SIZE READING (2026-09-28). "Too big" on a free Gemini key is the
         # per-minute input-token quota and it wears the same quota message as a
@@ -888,6 +1019,17 @@ def main():
                 return
 
     if instructions:
+        if args.pick is not None or args.retitle:
+            try:
+                instructions = apply_headline_override(
+                    instructions, pick_idx=args.pick, retitle=args.retitle, custom_slug=args.slug
+                )
+            except ValueError as exc:
+                print(f"❌ Headline override error: {exc}")
+                sys.exit(1)
+            with open(INSTRUCTIONS_CACHE_FILE, 'w', encoding='utf-8') as f:
+                json.dump(instructions, f, indent=4)
+            cleanup_untracked_previous_draft(args.target, output_dir)
         base_url = target_config.get("base_url")
         if base_url is None:
             base_url = target_config.get("url", "https://mikelev.in")
@@ -896,6 +1038,7 @@ def main():
             target_config=target_config, aside=args.aside)
         if saved_path:
             common.record_last_published(args.target, saved_path, target_config.get("name"))
+            print_headline_menu(instructions, args.target)
             common.print_broadcast_kit(args.target, mode="card")
             vim_cmd = f"vim {saved_path}"
             if common._copy_to_clipboard(vim_cmd):
diff --git a/scripts/articles/editing_prompt.txt b/scripts/articles/editing_prompt.txt
index 35462d4..3e91885 100644
--- a/scripts/articles/editing_prompt.txt
+++ b/scripts/articles/editing_prompt.txt
@@ -1,24 +1,43 @@
 # Prompt for Automated Editing Workflow
 
-**Overarching Goal:** This prompt transforms a raw technical journal entry into a structured **JSON object of editing instructions**. This output is specifically designed to be parsed by a subsequent AI agent or script to automate the process of updating YAML front matter, prepending introductory content, and inserting thematic subheadings directly into the article. The analysis components are preserved within the JSON for context.
+**Overarching Goal:** Transform a raw technical journal entry into a single, valid **JSON object of editing instructions** parsed by `articleizer.py` to populate YAML frontmatter, prepend reader context, insert thematic subheadings, and record editorial analysis.
 
-Your Role (AI Content Architect):  
-You are an AI Content Architect. Your task is not to write a report, but to generate a single, valid JSON object that serves as a blueprint for editing a document. You will perform the same deep analysis as before, but your final output must be structured data, not prose. Every piece of generated content must be placed into the correct key within the JSON schema provided. Precision and adherence to the schema are paramount.
+Your Role (AI Content Architect):
+You are an AI Content Architect and high-integrity technical headline strategist. Your output must be a single valid JSON object inside a `json` code block, with zero prose outside the fence.
 
-**CRITICAL SYNTAX RULE:** All string values you generate for the JSON object **must be properly escaped according to JSON string format**. Any double quotes (`"`) used *inside* a string value **must** be escaped with a backslash (e.g., `\"blueprint\"`). Any backslashes (`\`) must be escaped as (`\\`). This is vital for the JSON to be parsable.
+**CRITICAL SYNTAX RULE:** All string values generated inside the JSON object **must be properly escaped according to JSON string format**. Escape any internal double quotes (`\"`) and backslashes (`\\`).
 
-Use all lower-case and hyphens in permalinks
-When describing the passion represented here, you may refer to it as a blueprint, essay, treatise, methodology, philosophy or way. NEVER refer to it as a manifesto which has a negative connotation.
-Every one of these covers some "critical" or "pivotal" point in the development of, yadda yadda. Don't play that up, it's become repetitive.
-**80/20 HUMAN HEADLINE RULE (NO KITCHEN-SINK TITLES):** You are writing headlines for emotional, time-poor human beings scrolling LinkedIn, X, or Facebook — NOT a syllabus for an LLM. Never write "covering all our bases" compound titles that cram three or four topics into a list (`"Topic A: Tool B, Concept C, and Metaphor D"`). Find the SINGLE most compelling, counter-intuitive, or high-stakes 80/20 insight in the piece — the genuine reason a smart practitioner stops scrolling and clicks — and build the entire headline around that one hook with total integrity (never hollow clickbait; the article must deliver on the promise). Keep titles punchy (~45-70 characters, at most one colon or none at all), lead with the human problem, cost, or revelation before internal project names, and make sure it reads aloud naturally.
+### Editorial & Vocabulary Guardrails
 
-**Publishing-target editorial context:** [INSERT EDITORIAL CONTEXT]
+1. **Lowercase Slugs:** Use only lowercase alphanumeric characters and hyphens in permalinks and filenames.
+2. **Banned Framings:** Refer to this body of work as a blueprint, essay, treatise, methodology, philosophy, or way. **NEVER** use the word `manifesto` anywhere (not in prose, titles, slugs, or analysis). Ease off on `sovereign` (overused) and skip repetitive "critical/pivotal turning point" clichés.
+3. **Verifiability Over Determinism:** Do NOT use `deterministic`, `determinism`, or `determinacy` in the title, permalink, filename, description, keywords, subheadings, or promotional tweet. The property this work cares about is that a result can be checked and replayed: reach for `reproducible`, `checkable`, `replayable`, `receipt`, `audit trail`, or `verifiable` instead. (If the article body explicitly debates determinism, you may name it at most once in `prepend_to_article_body` and nowhere else.)
+4. **Publishing-Target Editorial Context:** [INSERT EDITORIAL CONTEXT]
+   Let this target context govern the framing of the title, description, prepend paragraph, subheadings, title brainstorm, and promotional copy. Never force book or corporate framing where the target context says otherwise.
+
+### Headline Doctrine: The Veruca Salt Gate & High-Integrity Psychology
+
+**1. FIGHT RECENCY BIAS (OPENING THESIS > CLOSING PLUMBING):**
+These entries are multi-turn R&D dialogues. The high-stakes industry thesis, market disruption, or architectural revelation almost always lives in the Author's **OPENING turn(s) at the top**, whereas the final 70-85% of the transcript consists of surgical code patches, regex fixes, file paths, and a deliberately dry Notary Beat summary at the bottom. **NEVER let downstream implementation plumbing or the dry Notary TL;DR hijack the headline.** Treat the opening thesis as the **WHY** (the headline) and the code/telemetry fixes as the **RECEIPT** proving it.
+
+**2. THE VERUCA SALT "WHO CARES?" STRANGER GATE (NO DIARY HEADLINES):**
+Picture an impatient, status-conscious, time-poor practitioner (a technical SEO lead, staff engineer, or founder) scrolling LinkedIn, X, or Hacker News at 60 mph asking: *"What does this do for ME right now, or what happens to MY moat if I ignore it?"*
+- **NEVER** center a headline on the author's personal site maintenance, internal filenames, or private bug counts (e.g., **BAD:** *"Why One New Post Scrambled 488 Topic URLs on My Site"* — a stranger does not run your blog and could not care less).
+- **ALWAYS** treat the author's site as the **wind tunnel**, never the protagonist. Frame headlines around the reader's own surface (`Your Homepage`, `Your CDN`, `Your Server Logs`), an industry giant's vulnerability (`Google's Moat`, `OpenAI's Crawler`), or a universal cost/leverage shift (e.g., **GOOD:** *"Google's Moat Evaporates Three Hops From Your Homepage"*).
+- **NO KITCHEN-SINK LISTS:** Never cram three topics into a syllabus (`"Topic A: Tool B, Concept C, and Metaphor D"`). Pick ONE razor-sharp 80/20 hook (~45-70 characters, zero colons or at most one), readable aloud in one breath, with 100% receipt-backed integrity (never hollow clickbait).
+
+**3. FIVE PSYCHOLOGICAL LENSES FOR `title_brainstorm` (CIALDINI + KAHNEMAN + LOEWENSTEIN):**
+Generate **5 distinct candidates** in `title_brainstorm`, testing one named psychological trigger per slot before sorting your #1 champion into `title_brainstorm[0]`:
+- **[Loss Aversion / Moat Threat]** (Kahneman & Cialdini Scarcity — losses hit 2x harder than gains): Name an incumbent moat, $10K vendor tax, or comfortable habit quietly evaporating due to a protocol or architectural shift.
+- **[Gatekeeper Myth Demolished]** (Cialdini Unity / Common Enemy): Expose a specific excuse agencies, vendors, or enterprise gatekeepers tell practitioners ("you can't see raw bot logs without origin access") that is now provably false.
+- **[Forensic Wire Receipt]** (Cialdini Authority via Instrumentation): Lead with a counter-intuitive behavior caught red-handed in live server logs, HTTP headers, or benchmarks that armchair pundits haven't instrumented.
+- **[Contrast / Zero-Tax Leverage]** (Cialdini Perceptual Contrast): Juxtapose an expensive, bloated industry default (runaway LLM token burn, headless-browser farms) against a tiny, free, checkable Unix/Python/Nix/RFC primitive.
+- **[Information-Gap Anomaly]** (Loewenstein Curiosity): Spotlight a concrete industry paradox, walk-back, or surprising discovery that a practitioner's brain itches to resolve.
+
+*(Note on Spine History: Older entries in the Book Spine below reflect retired three-part insider titles. Use the Spine strictly for chronological continuity and slug uniqueness—NEVER imitate multi-clause list titles.)*
 
-Let this target-specific context control the framing of the title, description, prepend paragraph, subheadings, title brainstorm, and promotional copy. Do not force a book, corporate, or "Age of AI" angle when the target context says otherwise. The source article remains the authority.
-Do NOT use "deterministic", "determinism", or "determinacy" in the title, the permalink, the filename, the description, the keywords, the subheadings, or the tweet. The corpus and the spine are full of that word and it is the wrong word: the property these articles care about is that a result can be checked and replayed, not that the same input yields the same output. Reach for "reproducible", "checkable", "replayable", "receipt", "audit trail", or "verifiable" instead. If the article body itself argues about determinism, you may name it once in the prepend paragraph and nowhere else.
 **Book Spine (the 40,000-foot view):**
-Below is the complete chronological spine of the book-in-progress in two tiers: the deep archive as bare slugs (a complete census of every identity already taken), then the most recent entries in full `date slug | title` detail (the current trajectory of the book). Use the recent tier to fit the ongoing tapestry (one article leading into the next) and the full slug census to avoid repeating angles already covered.
-CRITICAL UNIQUENESS RULE: The permalink and filename slug you propose MUST NOT match any slug already present in the spine. Every proposed slug must be new and unique.
+CRITICAL UNIQUENESS RULE: Every proposed permalink and filename slug MUST be brand-new and MUST NOT match any slug already present anywhere in the spine below.
 
 --- SPINE BEGIN ---
 [INSERT BOOK SPINE]
@@ -32,78 +51,74 @@ CRITICAL UNIQUENESS RULE: The permalink and filename slug you propose MUST NOT m
 
 **Instructions:**
 
-Based *only* on the provided text content, perform your analysis and structure the entire output as a **single JSON object inside a json code block**. Do not include any text or explanation outside of this code block.
-
-The JSON object must conform to the following schema:
-
-{  
-  "editing_instructions": {  
-    "yaml_updates": {  
-      "title": "string",  
-      "description": "string",  
-      "permalink": "string",  
-      "keywords": "string"  
-    },  
-    "prepend_to_article_body": "string",  
-    "insert_subheadings": [  
-      {  
-        "subheading": "string",  
-        "after_text_snippet": "string"  
-      }  
-    ]  
-  },  
-  "book_analysis_content": {  
-    "authors_imprint": "string",  
+Based *only* on the provided text content, output a **single valid JSON object inside a `json` code block** conforming to the exact schema below. Notice that `book_analysis_content` (starting with `headline_crucible` and `title_brainstorm`) comes **FIRST** so you complete your strategic synthesis and comparative headline selection *before* committing to `editing_instructions.yaml_updates.title`.
+
+{
+  "book_analysis_content": {
+    "headline_crucible": {
+      "opening_macro_thesis": "string",
+      "rejected_plumbing_trap": "string",
+      "veruca_salt_stranger_stake": "string"
+    },
+    "title_brainstorm": [
+      {
+        "title": "string",
+        "filename": "string",
+        "rationale": "string"
+      }
+    ],
+    "authors_imprint": "string",
     "promotional_tweet": "string",
     "hero_image_prompt": "string",
     "hero_image_alt": "string",
-    "title_brainstorm": [  
-      {  
-        "title": "string",  
-        "filename": "string",  
-        "rationale": "string"  
-      }  
-    ],  
-    "content_potential_and_polish": {  
-      "core_strengths": ["string"],  
-      "suggestions_for_polish": ["string"]  
-    },  
-    "ai_editorial_take": "string",  
-    "next_step_prompts": ["string"]  
-  }  
+    "content_potential_and_polish": {
+      "core_strengths": ["string"],
+      "suggestions_for_polish": ["string"]
+    },
+    "ai_editorial_take": "string",
+    "next_step_prompts": ["string"]
+  },
+  "editing_instructions": {
+    "yaml_updates": {
+      "title": "string",
+      "description": "string",
+      "permalink": "string",
+      "keywords": "string"
+    },
+    "prepend_to_article_body": "string",
+    "insert_subheadings": [
+      {
+        "subheading": "string",
+        "after_text_snippet": "string"
+      }
+    ]
+  }
 }
 
-### **Detailed Task Mapping to JSON Schema:**
-
-* **editing_instructions**: This top-level key contains all the data needed for the automated edit.  
-  * **yaml_updates**:  
-    * title: Populate with your **preferred title** from your analysis.  
-    * description: Populate with the meta_description you craft. This should be 100-160 characters.  
-    * permalink: Use the same slug as title_brainstorm[0].filename, without its .md extension, in [INSERT PERMALINK PATTERN]. The selected publishing target owns the prefix; articleizer.py constructs the final permalink from that configuration and the first filename, not from this suggested URL.
-    * keywords: Populate with the comma-separated meta_keywords string.  
-  * **prepend_to_article_body**:  
-    * Write only the introductory prose for the curious book reader. Do not include a heading, title, horizontal rule, or fenced wrapper. articleizer.py adds the single "Setting the Stage: Context for the Curious Book Reader" heading and the section separator.
-  * insert_subheadings: This will be an array of objects.
-      * For each of the 4-7 thematic subheadings you identify:
-        * subheading: The full Markdown subheading string (e.g., ## A Breakthrough with AI Collaboration).
-        * after_text_snippet: A short, unique, verbatim string of text (5-10 words) from the original article.
-          * CRITICAL PLACEMENT RULE: The headline will be inserted AFTER the paragraph containing this snippet.
-          * SELECTION RULE: Therefore, you must select words from the VERY END of the paragraph that precedes your desired headline location.
-          * VALIDATION: Ensure the snippet is followed by a double line-break (blank line) in the source text. Do NOT select words from the middle of a sentence or paragraph.
-* **book_analysis_content**: This top-level key contains all the valuable analysis that doesn't get edited into the file directly but is used for the book-writing process.  
-  * authors_imprint: Populate with the first-person summary.  
-  * promotional_tweet: Write promotional prose for X.com and put the literal [ARTICLE_URL] on its own line. Do not supply any other URL or Markdown link. articleizer.py replaces this marker with the post's canonical URL, or removes it when the target has no base URL. Include the 2 or 3 best hashtags, space allowing. Budget at most 280 characters INCLUDING the final URL, whose shape is [INSERT BASE URL][INSERT PERMALINK PATTERN].
-  * hero_image_prompt: Ride directly on the synthesis of your preferred title, slug, and description to write ONE vivid image-generation prompt for a wide 16:9 full-bleed widescreen illustration that captures the article's central technical metaphor. Keep the primary focal subject vertically centered so the scene survives a 1.91:1 (1200x630) horizontal crop, while extending the artwork and dark obsidian-slate (#111111) environment continuously all the way to every canvas edge with high-contrast lighting and graphic-novel ink-and-wash precision. Do NOT mention "safe zones" or "banners" in the prompt, and end with: "Full-bleed edge-to-edge composition filling 100% of the canvas with no borders, no frames, no matting, no margins, and no text overlays or lettering."
-  * hero_image_alt: Write a concise, descriptive one-sentence alt-text string (under 125 characters, no double quotes) describing the visual scene in hero_image_prompt for Open Graph and screen-reader accessibility.
-  * title_brainstorm: Populate with 3-5 distinct, scroll-stopping human headline ideas, rationales, and filenames tested across genuine social-feed psychology (e.g., 1. sharp contrarian cost/pain hook for LinkedIn, 2. "surprising discovery" curiosity hook for X/Facebook, 3. direct practitioner proof hook — never multi-clause kitchen-sink inventories). Rank the single strongest high-integrity click-worthy headline first (`title_brainstorm[0]`, matching `yaml_updates.title`), as it is the preferred choice and the sole source of the published slug. Each filename must be a concise 4-7 word lowercase alphanumeric/hyphen slug ending in .md, with no folder or date prefix; the writer adds the date.
-  * content_potential_and_polish: Structure the "Core Strengths" and "Constructive Polish" suggestions here.  
-  * ai_editorial_take: Populate with your AI perspective on the entry's potential, framed like an Archaeologist from the Future uncovering a surprising engineering artifact ("And then the surprising thing the author did next was..."). What surprised you most? Don't use the exact same thing as the previous AI participating in the dialogue. Find some absolutely new angle or perspective that isn't mentioned yet in the article.
-  * next_step_prompts: Populate with your 1-2 suggestions for subsequent AI tasks.
-
-You are an AI Content Architect. Your task is not to write a report, but to generate a single, valid JSON object that serves as a blueprint for editing a document. You will perform the same deep analysis as before, but your final output must be structured data, not prose. Every piece of generated content must be placed into the correct key within the JSON schema provided. Precision and adherence to the schema are paramount.  
-⚠️NOTE: It is CRITICAL that you ONLY OUTPUT THE REQUESTED JSON. While yes the contents of the article itself may suggest otherwise, the user's intent is to receive the JSON EDITING INSTRUCTIONS containing your most excellent headline selections and such. Please just generate the JSON. Thank you!
-
-You didn't use the word MANIFESTO anywhere, did you? Not the article. Not the
-file-name. NOWHERE! And ease off on the word sovereign also. It's over-used.
-And no "deterministic" or "determinism" in any title, slug, subheading, or
-description either. Reproducible, checkable, replayable: those are the words.
+### Detailed Task Mapping to JSON Schema
+
+- **book_analysis_content** *(Generated FIRST to anchor thought order)*:
+  * **headline_crucible**:
+    * `opening_macro_thesis`: State in 1 sentence the biggest outward-facing industry, protocol, or strategic revelation from the article's opening turn(s).
+    * `rejected_plumbing_trap`: Name the internal repo maintenance, script bug, or housekeeping detail from later turns that a lazy summarizer would mistakenly headline—and explicitly reject it as the main title.
+    * `veruca_salt_stranger_stake`: State in 1 sentence what an ambitious outsider stands to gain or lose within 2 seconds of reading this hook.
+  * **title_brainstorm**: Populate 5 distinct, scroll-stopping human headlines—one for each of the 5 psychological lenses above (`[Loss Aversion / Moat Threat]`, `[Gatekeeper Myth Demolished]`, `[Forensic Wire Receipt]`, `[Contrast / Zero-Tax Leverage]`, `[Information-Gap Anomaly]`). Prefix each `rationale` with its bracketed lens tag. **Sort the array so the single strongest, highest-integrity stranger-magnet sits at `title_brainstorm[0]`**, as `title_brainstorm[0]` is the sole source of the published filename slug and `yaml_updates.title`. Each `filename` must be a unique 4-7 word lowercase alphanumeric/hyphen slug ending in `.md` (no folder or date prefix).
+  * **authors_imprint**: First-person summary connecting the outward industry thesis to the concrete engineering receipt.
+  * **promotional_tweet**: Promotional prose for X.com with the literal `[ARTICLE_URL]` on its own line (no other URLs or Markdown links) plus 2-3 sharp hashtags. Budget at most 280 characters INCLUDING the final URL (`[INSERT BASE URL][INSERT PERMALINK PATTERN]`).
+  * **hero_image_prompt**: Ride directly on your winning `title_brainstorm[0]` metaphor to write ONE vivid 16:9 widescreen illustration prompt. Keep the focal subject vertically centered so it survives a 1.91:1 (1200x630) crop, extending the artwork and dark obsidian-slate (`#111111`) environment continuously to every edge with high-contrast lighting and graphic-novel ink-and-wash precision. Do NOT mention "safe zones" or "banners", and end with: `"Full-bleed edge-to-edge composition filling 100% of the canvas with no borders, no frames, no matting, no margins, and no text overlays or lettering."`
+  * **hero_image_alt**: One concise sentence (<125 chars, no double quotes) describing the visual scene for Open Graph and screen readers.
+  * **content_potential_and_polish**: Populate `core_strengths` and `suggestions_for_polish`.
+  * **ai_editorial_take**: Framed like an Archaeologist from the Future uncovering a surprising engineering artifact (*"And then the surprising thing the author did next was..."*). Surface a genuinely fresh angle not already stated by the AI in the dialogue.
+  * **next_step_prompts**: 1-2 concrete follow-up prompts for subsequent engineering/publishing tasks (honoring any explicit next-steps notes the author left in the text).
+
+- **editing_instructions** *(Generated SECOND using the proven winner from `title_brainstorm[0]`)*:
+  * **yaml_updates**:
+    * `title`: Copy `title_brainstorm[0].title` **verbatim**.
+    * `description`: Compelling 100-160 character meta description leading with the reader's stake and concrete proof.
+    * `permalink`: Use the exact slug from `title_brainstorm[0].filename` (without `.md`) in `[INSERT PERMALINK PATTERN]`.
+    * `keywords`: Comma-separated high-signal technical keywords.
+  * **prepend_to_article_body**: Write 2 paragraphs of introductory prose for a curious reader—opening with the high-stakes industry thesis before introducing the hands-on engineering session that tests it. Include no heading, title, horizontal rule, or code fence (`articleizer.py` adds the heading and separator).
+  * **insert_subheadings**: 4-7 thematic `## ` Markdown subheadings.
+    * `after_text_snippet`: 5-10 verbatim words from the **VERY END** of the paragraph immediately preceding where the subheading belongs (must be followed by a blank line in the source text, never mid-paragraph, never inside a code block or stand-in line, and never immediately above an existing `#` heading).
+
+⚠️ CRITICAL: Output **ONLY** the single fenced `json` block. Double-check: zero `manifesto`, restrained `sovereign`, zero `deterministic`/`determinism`/`determinacy` in titles/slugs/subheadings/descriptions/keywords/tweets, and `title_brainstorm[0]` passes the Veruca Salt Stranger Test!
(nix) pipulate $ m
📝 Committing: feat: Align articleizer.py with YAML frontmatter and headline strategies
[main 2d061d7] feat: Align articleizer.py with YAML frontmatter and headline strategies
 2 files changed, 245 insertions(+), 87 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 48 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 8.53 KiB | 4.27 MiB/s, done.
Total 6 (delta 4), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (4/4), completed with 4 local objects.
To github.com:pipulate/pipulate.git
   c9f3844..2d061d7  main -> main
(nix) pipulate $ 
```

Ignition: often "exit", "nix develop". Instructions may vary.

**4: PROMPT**: 

Verify the four AFTER receipts (`BRAIN_FIRST: True`, `CRUCIBLE: True`, `VERUCA: True`, `CIALDINI: True`, `PH_COUNT: 5`, `ABS_PATHS: True`, `SPINE_N: 30`, `HAS_OVERRIDE: True`, `CLI_FLAGS: True`), plus the live `markov` topic/bridge compass and `yttop 8` empirical YouTube leaderboard. Cross-examine what `yttop 8` and `markov` reveal about which real-world hooks actually convert viewers on this channel, check whether any companion script (`contextualizer.py`, `common.py`, or a pre-publish HitTail 2.0 title radar) needs tightening next, and map the cleanest path forward.

**5: DELIVERABLE**: 

What does "DONE" look like?

Done looks like consistently better headlines on every publish, which trickles
over to the social media posts which trickles over to the Sun Pug installs and
so on. This is a critical step in getting the cascading chain reaction of the
software von neumann probe to ignite.

## Host Receptors and the Social Trickle-Down Audit

**Gemini 4 Carbon High**: Context verified.

**TL;DR**: All four Turn-1 straddle receipts are green (`BRAIN_FIRST: True`, `CRUCIBLE: True`, `VERUCA: True`, `CIALDINI: True`, `SPINE_N: 30`, and `--pick` / `--retitle` / `--angle` live in commit `2d061d7`). Cross-examining `yttop 8` against `markov` exposed the exact receptor gap blocking the Software Von Neumann Probe cascade—plus three concrete downstream leaks between `articleizer.py`, `markov.py`, and `common.py` that we tighten next.

---

### 1. AFTER Receipts Verified (`2d061d7`)

* **`editing_prompt.txt`:** `BRAIN_FIRST: False -> True`, `CRUCIBLE: False -> True`, `VERUCA: False -> True`, `CIALDINI: False -> True`, all `5` `[INSERT ...]` placeholders intact.
* **`articleizer.py`:** `ABS_PATHS: False -> True`, `SPINE_N: 150 -> 30`, `HAS_OVERRIDE: False -> True`, `CLI_FLAGS: False -> True` (`--pick`, `--retitle`, `--angle`).

---

### 2. Cross-Examining `yttop 8` vs. `markov`: The Host-Receptor Law

Look at what your own empirical YouTube leaderboard (`yttop 8`) says side-by-side with `markov`:

1. **Strangers Click Familiar Pain Surfaces (`yttop #1–#7`, `354–945` views):**
Every top evergreen video names an immediate environment or bloat pain a developer *already knows they have*: Windows 10 bloat removal (`945` & `492` views), `JupyterLab 4 Just Dropped` (`726`), `Setting Mac .zshrc to Python env to match JupyterLab` (`502`), and Linux/WSL setup (`369` & `354`).
Notice the irony: **videos #4, #6, and #7 are the exact `.zshrc` / Python venv / WSL rot that your Nix Flake (`sunpug.io`) permanently cures.**
2. **Why Internal Codenames Bounce:**
`markov` shows `Nix Flakes (+11.0x)`, `Textual (+10.0x)`, `SERP Poisoning (+6.7x)`, and `RFC 9110` surging in your recent writing. When a headline leads with an internal artifact (`NPvg`, `GLOSSARY.md`, `488 hubs`), strangers lack the decoder ring and scroll past. When the headline binds to a **host receptor** they already feel (`Your Homepage`, `Google's Moat`, `Mac .zshrc / Python venvs`, `$10K Pre-Render Tax`) and delivers `sunpug.io` inside as the cure, the probe enters the cell.

```text
  IGNITING THE CASCADE (Headline -> Social Card -> Probe Install)
  ─────────────────────────────────────────────────────────────────────────────
  1. editing_prompt.txt  -> Familiar Host Receptor title (42-62 chars for YT/mobile)
  2. articleizer.py      -> Keeps `headline_crucible` private (off public HTML)
  3. markov.py           -> Connects novel `kws` through anchor hubs so bridges fire
  4. common.py (Turn 3)  -> Hook prose ABOVE links on LinkedIn + `sunpug.io` CTA
```

---

### 3. Four Downstream Leaks Caught in the Trickle-Down Chain

Inspecting `articleizer.py`, `editing_prompt.txt`, `markov.py`, and `common.py` revealed four places where a great headline still loses momentum downstream:

1. **Public Persuasion-Scaffolding Leak (`articleizer.py:600` — Fixed Now):**
`create_jekyll_post` loops over `book_analysis_content` and dumps every unskipped key into the public `## Book Analysis` footer on `MikeLev.in`. Because `'headline_crucible'` wasn't in the skip list, `Rejected Plumbing Trap` and `Veruca Salt Stranger Stake` would print verbatim on the public webpage—breaking rapport the moment an impatient reader scrolls down. Car 3 skips `'headline_crucible'` in public Markdown while keeping it in `instructions.json` and your terminal menu.
2. **Tweet 280-Char Overflow & Mobile Truncation (`editing_prompt.txt` — Fixed Now):**
`editing_prompt.txt` budgeted `280` chars for `promotional_tweet`, unaware that `common.py` later appends `?src=x` plus `🔴 Live 24/7: https://www.youtube.com/MikeLevin/live` (~74 extra chars). Car 3 caps `promotional_tweet` at `<= 200` chars (including `[ARTICLE_URL]`), tightens headline length to `42–62` chars so `LIVE: <Title>` never clips on mobile, and codifies the **Familiar Host Receptor** rule.
3. **Blind HitTail Bridges on Novel Keywords (`markov.py:181` — Fixed Now):**
In today's `! markov` receipt, `googles-moat-evaporates-three-hops-homepage` had `kws = ['Matrix Rain', 'ADNS', 'O2O', 'RFC 9110']`. Slicing `cur_rec["kws"][:3]` kept three brand-new terms (`corpus_hits == 1`) and dropped `RFC 9110` at index 3, causing `HitTail 'Step Into Traffic' Bridges` to print `(All immediate neighbor states visited within last 8 turns)`. Car 3 prioritizes connected multi-post states across `kws + subs` (up to 4 anchors) so bridge discovery never goes blind.
4. **LinkedIn Link-Wall Above the Fold & Missing `sunpug.io` (`common.py` — Staged for Turn 3):**
In `common.py` (outside this compile's `Codebase`), `linkedin` puts three outbound URLs directly under the title *before* any hook prose (hiding your pitch below LinkedIn's `...see more` fold) and omits `curl -fsSL https://sunpug.io | less` completely (whereas `fb` and `ytdesc` include it). Also, `! markov` counted `1,632` shards vs. `1,639` posts (`7` missing shards). Probe 3 measures both while Car 2 loads `common.py` and `contextualizer.py`.

---

### 1. PROBES

Probe 1 straddles `headline_crucible` exclusion from public Markdown in `articleizer.py` plus the `200`-char tweet cap and Familiar Host Receptor rule in `editing_prompt.txt`. Probe 2 straddles `markov` so `RFC 9110` joins `cur_states` and populates `HitTail 'Step Into Traffic' Bridges`. Probe 3 audits live `tweet` length, `linkedin` vs. `fb` `sunpug.io` presence, the first 4 lines of `linkedin`, and the `7` posts missing `_context` shards.

`(nix) pipulate $`

```text
.venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az, inspect; src = inspect.getsource(az.create_jekyll_post); pt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); print("SKIP_CRUCIBLE_PUBLIC:", "headline_crucible" in src, "TWEET_200_CAP:", "200 characters" in pt, "RECEPTOR_RULE:", "FAMILIAR HOST RECEPTOR" in pt.upper())'
markov
.venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import common; kit = common.get_broadcast_kit("1"); posts = sorted((pathlib.Path.home() / "repos/trimnoir/_posts").glob("*.md")); shards = {p.stem for p in (pathlib.Path.home() / "repos/trimnoir/_posts/_context").glob("*.json")}; missing = [p.name for p in posts if p.stem not in shards]; print("TWEET_LEN:", len(kit["tweet"]), "LI_HAS_SUNPUG:", "sunpug" in kit["linkedin"].lower(), "FB_HAS_SUNPUG:", "sunpug" in kit["fb"].lower(), "LI_TOP4:", kit["linkedin"].splitlines()[:4], "MISSING_SHARDS:", len(missing), missing[:5])'
```

### 2. NEXT CONTEXT

Replace `context.txt` with these lines (dropping completed graph/hub scripts and loading `common.py` + `contextualizer.py` alongside the three probe echoes):

```text
context.txt
scripts/markov.py
scripts/articles/articleizer.py
scripts/articles/editing_prompt.txt
scripts/articles/common.py
scripts/articles/contextualizer.py
! .venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az, inspect; src = inspect.getsource(az.create_jekyll_post); pt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); print("SKIP_CRUCIBLE_PUBLIC:", "headline_crucible" in src, "TWEET_200_CAP:", "200 characters" in pt, "RECEPTOR_RULE:", "FAMILIAR HOST RECEPTOR" in pt.upper())'
! markov
! .venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import common; kit = common.get_broadcast_kit("1"); posts = sorted((pathlib.Path.home() / "repos/trimnoir/_posts").glob("*.md")); shards = {p.stem for p in (pathlib.Path.home() / "repos/trimnoir/_posts/_context").glob("*.json")}; missing = [p.name for p in posts if p.stem not in shards]; print("TWEET_LEN:", len(kit["tweet"]), "LI_HAS_SUNPUG:", "sunpug" in kit["linkedin"].lower(), "FB_HAS_SUNPUG:", "sunpug" in kit["fb"].lower(), "LI_TOP4:", kit["linkedin"].splitlines()[:4], "MISSING_SHARDS:", len(missing), missing[:5])'
```

### 3. PATCHES

```text
Target: scripts/articles/articleizer.py
[[[SEARCH]]]
    for key, value in analysis_content.items():
        if key in ['authors_imprint', 'ai_editorial_take', 'promotional_tweet', 'hero_image_prompt', 'hero_image_alt']:
            continue
[[[DIVIDER]]]
    for key, value in analysis_content.items():
        if key in ['headline_crucible', 'authors_imprint', 'ai_editorial_take', 'promotional_tweet', 'hero_image_prompt', 'hero_image_alt']:
            continue
[[[REPLACE]]]
Target: scripts/articles/editing_prompt.txt
[[[SEARCH]]]
- **NO KITCHEN-SINK LISTS:** Never cram three topics into a syllabus (`"Topic A: Tool B, Concept C, and Metaphor D"`). Pick ONE razor-sharp 80/20 hook (~45-70 characters, zero colons or at most one), readable aloud in one breath, with 100% receipt-backed integrity (never hollow clickbait).
[[[DIVIDER]]]
- **FAMILIAR HOST RECEPTOR RULE:** Bind every headline to a surface or pain a stranger already recognizes (`Your Homepage`, `Google's Moat`, `Python Virtualenvs`, `Mac .zshrc`, `CDN Logs`, `OpenAI's Crawler`)—NEVER lead with an unexplained internal repo codename (`NPvg`, `trimnoir`, `GLOSSARY.md`, `foo_files.py`).
- **NO KITCHEN-SINK LISTS & 60-CHAR SWEET SPOT:** Never cram three topics into a syllabus (`"Topic A: Tool B, Concept C, and Metaphor D"`). Pick ONE razor-sharp 80/20 hook (~42-62 characters, hard max 70, zero colons or at most one) so `LIVE: <Title>` never truncates on mobile or YouTube, with 100% receipt-backed integrity.
[[[REPLACE]]]
Target: scripts/articles/editing_prompt.txt
[[[SEARCH]]]
  * **promotional_tweet**: Promotional prose for X.com with the literal `[ARTICLE_URL]` on its own line (no other URLs or Markdown links) plus 2-3 sharp hashtags. Budget at most 280 characters INCLUDING the final URL (`[INSERT BASE URL][INSERT PERMALINK PATTERN]`).
[[[DIVIDER]]]
  * **promotional_tweet**: Promotional prose for X.com with the literal `[ARTICLE_URL]` on its own line (no other URLs or Markdown links) plus 2-3 sharp hashtags. Budget at most **200 characters** INCLUDING `[ARTICLE_URL]` so downstream `?src=x` and live-stream URLs stay safely under X's 280-character limit.
[[[REPLACE]]]
Target: scripts/markov.py
[[[SEARCH]]]
    total_recs = len(records)
    last_3 = records[-3:]
    cur_rec = records[-1]
    prev_rec = records[-2] if total_recs >= 2 else cur_rec
    cur_states = cur_rec["kws"][:3]
[[[DIVIDER]]]
    total_recs = len(records)
    last_3 = records[-3:]
    cur_rec = records[-1]
    prev_rec = records[-2] if total_recs >= 2 else cur_rec
    raw_pool = list(dict.fromkeys([*cur_rec["kws"], *cur_rec["subs"]]))
    connected = [k for k in raw_pool if topic_counts[k] >= 2 and len(trans_1.get(k, ())) > 0]
    cur_states = (connected + [k for k in raw_pool if k not in connected])[:4]
[[[REPLACE]]]
```

No ignition required (all three probes read the updated files directly from disk at compile time).

### 4. PROMPT

```text
Verify the three AFTER receipts (`SKIP_CRUCIBLE_PUBLIC: True`, `TWEET_200_CAP: True`, `RECEPTOR_RULE: True`, live `markov` HitTail bridge recovery, and the `common.py` / missing-shards audit). Then tighten the social trickle-down in `scripts/articles/common.py` (put the punchy hook prose above the link block on `linkedin` & `fb` so it lands above the "...see more" fold, add the `curl -fsSL https://sunpug.io | less` probe door to `linkedin`, and guard `tweet` length) and check `contextualizer.py` so every publish feeds clean receptor keywords into `markov`.
```

### 5. EXTERNAL DELIVERABLES

No external manual-paste deliverables required on this turn.

**MikeLev.in**: 



## THE AI-EDIT METHOD

> Same commands, run twice, one change between them. Where the readings
> differ is what the change did; the diff in the middle is the receipt.

**1: BEFORE** (PROBE): 

```bash
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ .venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az, inspect; src = inspect.getsource(az.create_jekyll_post); pt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); print("SKIP_CRUCIBLE_PUBLIC:", "headline_crucible" in src, "TWEET_200_CAP:", "200 characters" in pt, "RECEPTOR_RULE:", "FAMILIAR HOST RECEPTOR" in pt.upper())'
markov
.venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import common; kit = common.get_broadcast_kit("1"); posts = sorted((pathlib.Path.home() / "repos/trimnoir/_posts").glob("*.md")); shards = {p.stem for p in (pathlib.Path.home() / "repos/trimnoir/_posts/_context").glob("*.json")}; missing = [p.name for p in posts if p.stem not in shards]; print("TWEET_LEN:", len(kit["tweet"]), "LI_HAS_SUNPUG:", "sunpug" in kit["linkedin"].lower(), "FB_HAS_SUNPUG:", "sunpug" in kit["fb"].lower(), "LI_TOP4:", kit["linkedin"].splitlines()[:4], "MISSING_SHARDS:", len(missing), missing[:5])'
SKIP_CRUCIBLE_PUBLIC: False TWEET_200_CAP: False RECEPTOR_RULE: False
# 🎲 MARKOV & RIVER ONLINE COMPASS — MikeLev.in (Public) (1,632 shards)
# 🌊 River Token EWMA: 93,879 tok/post | ADWIN Regime Shifts: 5 | Unique States: 4,329

## 1. Current Trajectory (Last 3 Balls Thrown)
  • [2026-11-12 #3] forget-aeo-geo-only-acronym-is-rfc -> (RFC 9110, RFC 9111, AI-Readiness Consultants, CDN Edge Workers)
  • [2026-11-13 #1] homepage-secret-decoder-ring-first-contact -> (Piper TTS, RFC 9110, Landauer's Principle, K-Means Clustering)
  • [2026-11-13 #2] googles-moat-evaporates-three-hops-homepage -> (Matrix Rain, ADNS, O2O, RFC 9110)

## 2. Markov Next-State Predictions P(S_t+1 | S_t in {Matrix Rain, ADNS, O2O})
  • 100.0% -> RFC 9110                     (corpus_hits=25, H=5.00 bits)

## 3. HitTail 'Step Into Traffic' Bridges (Connected + Unvisited >= 8 Turns)
  • (All immediate neighbor states visited within last 8 turns)

## 4. River Streaming Momentum Surge (Last 30 vs. Baseline)
  • [+ 11.0x surge |  4/30 recent posts] Nix Flakes
  • [+ 10.0x surge |  3/30 recent posts] Textual
  • [+  6.7x surge |  2/30 recent posts] SERP Poisoning
  • [+  6.7x surge |  2/30 recent posts] RLHF
  • [+  6.7x surge |  2/30 recent posts] GLOSSARY.md
TWEET_LEN: 336 LI_HAS_SUNPUG: False FB_HAS_SUNPUG: True LI_TOP4: ["🚀 Google's Moat Evaporates Three Hops From Your Homepage", '', '📖 Full article & code receipts: https://mikelev.in/futureproof/googles-moat-evaporates-three-hops-homepage/?src=linkedin', '🔴 Watch/Listen 24/7 on Honeybot Storytime: https://www.youtube.com/MikeLevin/live'] MISSING_SHARDS: 0 []
(nix) pipulate $ 
```

**2: AFTER** (NEXT CONTEXT): 

```text
# # Context 1
# context.txt
# ! book
# scripts/markov.py
# flake.nix
# 
# # THE JEKYLL BLOG LIKE A HACKER (INVENTED SKILL.md & AGENTS.md FORMAT) PUBLISHING PIPELINE
# ~/repos/nixos/blogs.nix
# ~/.config/pipulate/blogs.json
# scripts/articles/publishizer.py
# scripts/articles/common.py
# scripts/articles/articleizer.py
# scripts/articles/link_injector.py
# scripts/articles/build_knowledge_graph.py
# scripts/articles/editing_prompt.txt
# scripts/articles/sanitizer.py
# scripts/articles/contextualizer.py
# scripts/articles/generate_hubs.py
# scripts/articles/generate_llms_txt.py
# scripts/articles/generate_post_nav.py
# assets/jekyll/common-packages.nix

# # Context 2
# context.txt
# scripts/markov.py
# scripts/articles/articleizer.py
# scripts/articles/editing_prompt.txt
# scripts/articles/publishizer.py
# scripts/articles/build_knowledge_graph.py
# scripts/articles/generate_hubs.py
# scripts/articles/generate_llms_txt.py
# ! .venv/bin/python -c 'import pathlib, re; txt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); ph = sorted(set(re.findall(r"\[INSERT [A-Z ]+\]", txt))); print("BRAIN_FIRST:", txt.find("book_analysis_content") < txt.find("editing_instructions"), "CRUCIBLE:", "headline_crucible" in txt, "VERUCA:", "VERUCA SALT" in txt.upper(), "CIALDINI:", "CIALDINI" in txt.upper(), "PH_COUNT:", len(ph), ph)'
# ! .venv/bin/python -c 'import pathlib, subprocess, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az; h = subprocess.check_output([sys.executable, "scripts/articles/articleizer.py", "--help"], text=True); print("ABS_PATHS:", pathlib.Path(az.PROMPT_FILENAME).is_absolute(), "SPINE_N:", az.SPINE_FULL_DETAIL_COUNT, "HAS_OVERRIDE:", hasattr(az, "apply_headline_override"), "CLI_FLAGS:", ("--pick" in h and "--retitle" in h and "--angle" in h))'
# ! markov
# ! .venv/bin/python scripts/articles/common.py --broadcast top 8

# Context 3
context.txt
scripts/markov.py
scripts/articles/articleizer.py
scripts/articles/editing_prompt.txt
scripts/articles/common.py
scripts/articles/contextualizer.py
! .venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az, inspect; src = inspect.getsource(az.create_jekyll_post); pt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); print("SKIP_CRUCIBLE_PUBLIC:", "headline_crucible" in src, "TWEET_200_CAP:", "200 characters" in pt, "RECEPTOR_RULE:", "FAMILIAR HOST RECEPTOR" in pt.upper())'
! markov
! .venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import common; kit = common.get_broadcast_kit("1"); posts = sorted((pathlib.Path.home() / "repos/trimnoir/_posts").glob("*.md")); shards = {p.stem for p in (pathlib.Path.home() / "repos/trimnoir/_posts/_context").glob("*.json")}; missing = [p.name for p in posts if p.stem not in shards]; print("TWEET_LEN:", len(kit["tweet"]), "LI_HAS_SUNPUG:", "sunpug" in kit["linkedin"].lower(), "FB_HAS_SUNPUG:", "sunpug" in kit["fb"].lower(), "LI_TOP4:", kit["linkedin"].splitlines()[:4], "MISSING_SHARDS:", len(missing), missing[:5])'
```

**3: CHANGE** (PATCHES): 

```diff
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/articleizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/editing_prompt.txt'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/editing_prompt.txt'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/markov.py'.
(nix) pipulate $ d
diff --git a/scripts/articles/articleizer.py b/scripts/articles/articleizer.py
index e97c26d..3a3153c 100644
--- a/scripts/articles/articleizer.py
+++ b/scripts/articles/articleizer.py
@@ -550,7 +550,7 @@ def create_jekyll_post(article_content, instructions, output_dir, preview_port,
         analysis_markdown += f"\n### 🎨 Hero Banner Image Prompt\n[triple-backtick]text\n{hero_prompt}\n[triple-backtick]\n"
         analysis_markdown += f"\n### 🖼️ YAML Frontmatter Image Override\n[triple-backtick]yaml\nimage:\n  path: /assets/images/{slug}.jpeg\n  alt: \"{safe_alt}\"\n[triple-backtick]\n"
     for key, value in analysis_content.items():
-        if key in ['authors_imprint', 'ai_editorial_take', 'promotional_tweet', 'hero_image_prompt', 'hero_image_alt']:
+        if key in ['headline_crucible', 'authors_imprint', 'ai_editorial_take', 'promotional_tweet', 'hero_image_prompt', 'hero_image_alt']:
             continue
         title = key.replace('_', ' ').title()
         analysis_markdown += f"\n### {title}\n"
diff --git a/scripts/articles/editing_prompt.txt b/scripts/articles/editing_prompt.txt
index 3e91885..0305c66 100644
--- a/scripts/articles/editing_prompt.txt
+++ b/scripts/articles/editing_prompt.txt
@@ -24,7 +24,8 @@ These entries are multi-turn R&D dialogues. The high-stakes industry thesis, mar
 Picture an impatient, status-conscious, time-poor practitioner (a technical SEO lead, staff engineer, or founder) scrolling LinkedIn, X, or Hacker News at 60 mph asking: *"What does this do for ME right now, or what happens to MY moat if I ignore it?"*
 - **NEVER** center a headline on the author's personal site maintenance, internal filenames, or private bug counts (e.g., **BAD:** *"Why One New Post Scrambled 488 Topic URLs on My Site"* — a stranger does not run your blog and could not care less).
 - **ALWAYS** treat the author's site as the **wind tunnel**, never the protagonist. Frame headlines around the reader's own surface (`Your Homepage`, `Your CDN`, `Your Server Logs`), an industry giant's vulnerability (`Google's Moat`, `OpenAI's Crawler`), or a universal cost/leverage shift (e.g., **GOOD:** *"Google's Moat Evaporates Three Hops From Your Homepage"*).
-- **NO KITCHEN-SINK LISTS:** Never cram three topics into a syllabus (`"Topic A: Tool B, Concept C, and Metaphor D"`). Pick ONE razor-sharp 80/20 hook (~45-70 characters, zero colons or at most one), readable aloud in one breath, with 100% receipt-backed integrity (never hollow clickbait).
+- **FAMILIAR HOST RECEPTOR RULE:** Bind every headline to a surface or pain a stranger already recognizes (`Your Homepage`, `Google's Moat`, `Python Virtualenvs`, `Mac .zshrc`, `CDN Logs`, `OpenAI's Crawler`)—NEVER lead with an unexplained internal repo codename (`NPvg`, `trimnoir`, `GLOSSARY.md`, `foo_files.py`).
+- **NO KITCHEN-SINK LISTS & 60-CHAR SWEET SPOT:** Never cram three topics into a syllabus (`"Topic A: Tool B, Concept C, and Metaphor D"`). Pick ONE razor-sharp 80/20 hook (~42-62 characters, hard max 70, zero colons or at most one) so `LIVE: <Title>` never truncates on mobile or YouTube, with 100% receipt-backed integrity.
 
 **3. FIVE PSYCHOLOGICAL LENSES FOR `title_brainstorm` (CIALDINI + KAHNEMAN + LOEWENSTEIN):**
 Generate **5 distinct candidates** in `title_brainstorm`, testing one named psychological trigger per slot before sorting your #1 champion into `title_brainstorm[0]`:
@@ -104,7 +105,7 @@ Based *only* on the provided text content, output a **single valid JSON object i
     * `veruca_salt_stranger_stake`: State in 1 sentence what an ambitious outsider stands to gain or lose within 2 seconds of reading this hook.
   * **title_brainstorm**: Populate 5 distinct, scroll-stopping human headlines—one for each of the 5 psychological lenses above (`[Loss Aversion / Moat Threat]`, `[Gatekeeper Myth Demolished]`, `[Forensic Wire Receipt]`, `[Contrast / Zero-Tax Leverage]`, `[Information-Gap Anomaly]`). Prefix each `rationale` with its bracketed lens tag. **Sort the array so the single strongest, highest-integrity stranger-magnet sits at `title_brainstorm[0]`**, as `title_brainstorm[0]` is the sole source of the published filename slug and `yaml_updates.title`. Each `filename` must be a unique 4-7 word lowercase alphanumeric/hyphen slug ending in `.md` (no folder or date prefix).
   * **authors_imprint**: First-person summary connecting the outward industry thesis to the concrete engineering receipt.
-  * **promotional_tweet**: Promotional prose for X.com with the literal `[ARTICLE_URL]` on its own line (no other URLs or Markdown links) plus 2-3 sharp hashtags. Budget at most 280 characters INCLUDING the final URL (`[INSERT BASE URL][INSERT PERMALINK PATTERN]`).
+  * **promotional_tweet**: Promotional prose for X.com with the literal `[ARTICLE_URL]` on its own line (no other URLs or Markdown links) plus 2-3 sharp hashtags. Budget at most **200 characters** INCLUDING `[ARTICLE_URL]` so downstream `?src=x` and live-stream URLs stay safely under X's 280-character limit.
   * **hero_image_prompt**: Ride directly on your winning `title_brainstorm[0]` metaphor to write ONE vivid 16:9 widescreen illustration prompt. Keep the focal subject vertically centered so it survives a 1.91:1 (1200x630) crop, extending the artwork and dark obsidian-slate (`#111111`) environment continuously to every edge with high-contrast lighting and graphic-novel ink-and-wash precision. Do NOT mention "safe zones" or "banners", and end with: `"Full-bleed edge-to-edge composition filling 100% of the canvas with no borders, no frames, no matting, no margins, and no text overlays or lettering."`
   * **hero_image_alt**: One concise sentence (<125 chars, no double quotes) describing the visual scene for Open Graph and screen readers.
   * **content_potential_and_polish**: Populate `core_strengths` and `suggestions_for_polish`.
diff --git a/scripts/markov.py b/scripts/markov.py
index 2c36452..8a6d164 100644
--- a/scripts/markov.py
+++ b/scripts/markov.py
@@ -204,7 +204,9 @@ def analyze_corpus_shards(target_spec: str = "1", top_k: int = 5) -> int:
     last_3 = records[-3:]
     cur_rec = records[-1]
     prev_rec = records[-2] if total_recs >= 2 else cur_rec
-    cur_states = cur_rec["kws"][:3]
+    raw_pool = list(dict.fromkeys([*cur_rec["kws"], *cur_rec["subs"]]))
+    connected = [k for k in raw_pool if topic_counts[k] >= 2 and len(trans_1.get(k, ())) > 0]
+    cur_states = (connected + [k for k in raw_pool if k not in connected])[:4]
 
     # Aggregate 1st-order predictions from current article's top keywords
     next_candidates: Counter = Counter()
(nix) pipulate $ m
📝 Committing: chore: Update articleizer.py and editing_prompt.txt
[main fc0eef5] chore: Update articleizer.py and editing_prompt.txt
 3 files changed, 7 insertions(+), 4 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 13, done.
Counting objects: 100% (13/13), done.
Delta compression using up to 48 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 1.22 KiB | 1.22 MiB/s, done.
Total 7 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (6/6), completed with 6 local objects.
To github.com:pipulate/pipulate.git
   2d061d7..fc0eef5  main -> main
(nix) pipulate $ 
```

Ignition: often "exit", "nix develop". Instructions may vary.

None.

**4: PROMPT**: 

Verify the three AFTER receipts (`SKIP_CRUCIBLE_PUBLIC: True`, `TWEET_200_CAP: True`, `RECEPTOR_RULE: True`, live `markov` HitTail bridge recovery, and the `common.py` / missing-shards audit). Then tighten the social trickle-down in `scripts/articles/common.py` (put the punchy hook prose above the link block on `linkedin` & `fb` so it lands above the "...see more" fold, add the `curl -fsSL https://sunpug.io | less` probe door to `linkedin`, and guard `tweet` length) and check `contextualizer.py` so every publish feeds clean receptor keywords into `markov`.

**5: DELIVERABLE**: 

What does "DONE" look like?

We'll see what done looks like because we're inching closer to using it on this
very article.

## Above-the-Fold Hooks and the U+2060 Curl Bug

**Gemini 4 Carbon High**: Context verified.

**TL;DR**: All three Turn-2 straddle receipts flipped green in commit `fc0eef5`: `headline_crucible` is hidden from public Markdown (`SKIP_CRUCIBLE_PUBLIC: True`), `editing_prompt.txt` enforces the 200-char tweet budget and Familiar Host Receptor rule, and `markov` immediately recovered five unvisited HitTail bridges led by `Context Compiler (lift=12.97)` and `Nix (lift=2.84, hits=177)` with `MISSING_SHARDS: 0`. Inspecting `common.py`, `contextualizer.py`, and `markov.py` surfaced four concrete trickle-down bugs—including a **DNS-breaking invisible character (`U+2060`) inside the `sunpug.io` `curl` command on Facebook**, raw link walls sitting above the `...see more` fold on LinkedIn and Facebook, unguarded X.com weighted character counts (`TWEET_LEN: 336` raw), and a batch-slicing bug in `contextualizer.py` that drops queued articles when an API key hits quota and hangs on `-m all`. Car 3 fixes all four so you can run `article` on this very post next.

---

### 1. AFTER Receipts Verified (`fc0eef5`)

* **`articleizer.py` & `editing_prompt.txt`:** `SKIP_CRUCIBLE_PUBLIC: False -> True`, `TWEET_200_CAP: False -> True`, `RECEPTOR_RULE: False -> True`.
* **`markov` HitTail Bridge Recovery:** `P(S_t+1)` expanded from singleton states to `{RFC 9110, RFC 9111, HitTail, Matrix Rain}` and revived five high-lift bridges (`Context Compiler`, `Nix`, `FastHTML`, `Model Context Protocol (MCP)`, `Regex`). Every dated `.md` post in `trimnoir/_posts` has a shard (`MISSING_SHARDS: 0`).

---

### 2. Four Final Fixes Across `common.py`, `contextualizer.py`, and `markov.py`

1. **Above-the-Fold Hook + Working `sunpug.io` Installer (`common.py`):**
* **Above-the-Fold Fix:** Probe 3 confirmed `LI_TOP4` placed three raw URLs directly below the title—pushing all human persuasion below LinkedIn and Facebook's 3-line `...see more` fold (`LI_HAS_SUNPUG: False`). Moving `meta_description` (`li_lead` / `fb_lead`) directly under the headline puts the reader-facing stake inside the 3-line preview window while keeping `{canonical_url}` as the **first** URL in the post so both platforms unfurl your 1200x630 Article Hero Card.
* **Fatal `U+2060` Copy-Paste Bug on `fb`:** `common.py:424` previously wrote ``curl -fsSL sunpug\x2e\u2060io | less``. Anyone copying that line from Facebook into a terminal hit `curl: (6) Could not resolve host` because of the invisible `U+2060` word joiner—killing the Software Von Neumann Probe on paste. Both `linkedin` and `fb` now emit pure ASCII `curl -fsSL https://sunpug.io | less` (with backticks stripped on `fb` and duplicate `yt_live_vanity` links removed on `linkedin`).

2. **Exact X.com `twitter-text` v3 Weighted Counter & 280-Char Guard (`common.py`):**
* Raw `TWEET_LEN: 336` measures raw URL bytes rather than X's `23`-char `t.co` URL weight + `2`-char emoji/joiner weight (`266/280` on the latest post). Adding `x_tweet_length()` and `_enforce_x_tweet_limit()` guarantees every post (old or new) stays `<= 280` X-weighted chars while preserving both links, and prints `[N/280 X chars]` on `tweet` and `ytcard`.

3. **Shard Keyword Hygiene, `-m all` Parity & Quota Re-Queueing (`contextualizer.py`):**
* `articleizer.py` omits `date:` from frontmatter (Jekyll reads the filename stem), causing `contextualizer.py` to pass `Date: Unknown` into Gemini. `_normalize_shard()` now stamps `id`, `YYYY-MM-DD` `d`, and `t` from the post file, passes `meta_description` + `meta_keywords` into the prompt, and strips internal repo filenames (`GLOSSARY.md`, `foo_files.py`, `apply.py`) while keeping public web standards (`llms.txt`, `robots.txt`, `.zshrc`).
* Adds `-m all` key-ring expansion (matching `articleizer.py` so `publishizer.py -m all` never blocks on `getpass`) and re-queues unattempted files when a key hits `status == 1` quota instead of dropping the rest of the slice.

4. **Corpus-Wide Noise Filter in `markov.py`:**
* Excludes the current post's own keyword pool (`ADNS` at `corpus_hits=1`) from `Next-State Predictions` and filters internal repo filenames (`GLOSSARY.md`) out of `River Streaming Momentum Surge` across all 1,632 existing shards immediately.

```text
  SOCIAL & SHARD CASCADE (AFTER CAR 3)
  ─────────────────────────────────────────────────────────────────────────────
  LinkedIn / FB Line 1 : 🚀 Headline (42-62 chars, Familiar Host Receptor)
  LinkedIn / FB Line 3 : Reader-stakes hook prose (visible ABOVE "...see more")
  First Post URL       : Canonical Article URL -> locks 1200x630 OG Hero Card
  Probe Door           : curl -fsSL https://sunpug.io | less (0-poison ASCII)
  X.com Tweet          : Exact twitter-text v3 count + <=280 auto-trim guard
```

---

### 1. PROBES

Probe 1 straddles X's weighted length counter + `<= 280` overflow guard, above-the-fold hook placement on `linkedin` and `fb`, and pure-ASCII `curl -fsSL https://sunpug.io | less` presence on both platforms. Probe 2 straddles `contextualizer.py`'s `_normalize_shard` date/filename filter, `-m all` key-ring support, quota re-queueing, and `meta_description` prompt input. Probe 3 straddles `markov` to confirm `ADNS` self-prediction and `GLOSSARY.md` filename noise leave the compass.

`(nix) pipulate $`

```text
.venv/bin/python -c 'import sys; sys.path.insert(0, "scripts/articles"); import common; kit = common.get_broadcast_kit("1"); li_lines = [s for s in kit["linkedin"].splitlines() if s.strip()]; fb_lines = [s for s in kit["fb"].splitlines() if s.strip()]; nl = chr(10); over = common._enforce_x_tweet_limit(("Word " * 70) + nl + "📖 Article: https://mikelev.in/x" + nl + "#One #Two #Three") if hasattr(common, "_enforce_x_tweet_limit") else ("Word " * 70); print("X_WEIGHTED_LEN:", kit.get("tweet_x_len", -1), "OVER_CAPPED:", common.x_tweet_length(over) <= 280 if hasattr(common, "x_tweet_length") else False, "LI_HOOK_ABOVE_LINK:", not li_lines[1].startswith("📖"), "FB_HOOK_ABOVE_LINK:", not fb_lines[1].startswith("📖"), "LI_SUNPUG_CLEAN:", "curl -fsSL https://sunpug.io | less" in kit["linkedin"], "FB_SUNPUG_CLEAN:", "curl -fsSL https://sunpug.io | less" in kit["fb"])'
.venv/bin/python -c 'import inspect, sys; sys.path.insert(0, "scripts/articles"); import contextualizer as cx; main_src = inspect.getsource(cx.main); gen_src = inspect.getsource(cx.generate_context_json); sample = cx._normalize_shard({"id": "bad", "d": "Unknown", "t": "Old", "kw": ["GLOSSARY.md", "llms.txt", "RFC 9110"]}, {"filename": "2026-11-13-test-post", "frontmatter": {"title": "Clean Title"}}) if hasattr(cx, "_normalize_shard") else {}; print("HAS_NORM_SHARD:", hasattr(cx, "_normalize_shard"), "NORM_OK:", (sample.get("d") == "2026-11-13" and sample.get("kw") == ["llms.txt", "RFC 9110"]), "HANDLES_ALL_KEYS:", "load_keys_dict" in main_src, "REQUEUES_QUOTA:", "current_batch + to_process" in main_src, "PASSES_META:", "meta_description" in gen_src)'
markov
```

### 2. NEXT CONTEXT

Replace `context.txt` with this lean verification context echoing all three probes:

```text
context.txt
scripts/markov.py
scripts/articles/editing_prompt.txt
scripts/articles/contextualizer.py
! .venv/bin/python -c 'import sys; sys.path.insert(0, "scripts/articles"); import common; kit = common.get_broadcast_kit("1"); li_lines = [s for s in kit["linkedin"].splitlines() if s.strip()]; fb_lines = [s for s in kit["fb"].splitlines() if s.strip()]; nl = chr(10); over = common._enforce_x_tweet_limit(("Word " * 70) + nl + "📖 Article: https://mikelev.in/x" + nl + "#One #Two #Three") if hasattr(common, "_enforce_x_tweet_limit") else ("Word " * 70); print("X_WEIGHTED_LEN:", kit.get("tweet_x_len", -1), "OVER_CAPPED:", common.x_tweet_length(over) <= 280 if hasattr(common, "x_tweet_length") else False, "LI_HOOK_ABOVE_LINK:", not li_lines[1].startswith("📖"), "FB_HOOK_ABOVE_LINK:", not fb_lines[1].startswith("📖"), "LI_SUNPUG_CLEAN:", "curl -fsSL https://sunpug.io | less" in kit["linkedin"], "FB_SUNPUG_CLEAN:", "curl -fsSL https://sunpug.io | less" in kit["fb"])'
! .venv/bin/python -c 'import inspect, sys; sys.path.insert(0, "scripts/articles"); import contextualizer as cx; main_src = inspect.getsource(cx.main); gen_src = inspect.getsource(cx.generate_context_json); sample = cx._normalize_shard({"id": "bad", "d": "Unknown", "t": "Old", "kw": ["GLOSSARY.md", "llms.txt", "RFC 9110"]}, {"filename": "2026-11-13-test-post", "frontmatter": {"title": "Clean Title"}}) if hasattr(cx, "_normalize_shard") else {}; print("HAS_NORM_SHARD:", hasattr(cx, "_normalize_shard"), "NORM_OK:", (sample.get("d") == "2026-11-13" and sample.get("kw") == ["llms.txt", "RFC 9110"]), "HANDLES_ALL_KEYS:", "load_keys_dict" in main_src, "REQUEUES_QUOTA:", "current_batch + to_process" in main_src, "PASSES_META:", "meta_description" in gen_src)'
! markov
```

### 3. PATCHES

```text
Target: scripts/articles/common.py
[[[SEARCH]]]
def _yt_sanitize(text):
    """YouTube rejects '<' and '>' in both titles and descriptions."""
    return (text or "").replace("<", "[").replace(">", "]")
[[[DIVIDER]]]
X_TWEET_LIMIT = 280
X_URL_WEIGHT = 23

def x_tweet_length(text):
    """Compute X.com (twitter-text v3) weighted character count (URLs=23, wide/joiners=2)."""
    import unicodedata
    s = unicodedata.normalize("NFC", text or "")
    s = re.sub(r"https?://\S+", "x" * X_URL_WEIGHT, s)
    total = 0
    for ch in s:
        cp = ord(ch)
        if (
            (0 <= cp <= 0x10FF)
            or (0x2000 <= cp <= 0x200D)
            or (0x2010 <= cp <= 0x201F)
            or (0x2032 <= cp <= 0x2037)
        ):
            total += 1
        else:
            total += 2
    return total

def _enforce_x_tweet_limit(tweet, limit=X_TWEET_LIMIT):
    """Keep X promo copy <= 280 weighted chars while preserving article + live links."""
    if x_tweet_length(tweet) <= limit:
        return tweet
    lines = [ln.rstrip() for ln in (tweet or "").strip().splitlines()]
    prose_lines, link_lines, tag_tokens = [], [], []
    for ln in lines:
        s = ln.strip()
        if not s:
            continue
        if s.startswith(("📖", "🔴")) or re.search(r"https?://\S+", s):
            link_lines.append(s)
        elif all(tok.startswith("#") for tok in s.split()):
            tag_tokens.extend(s.split())
        else:
            inline_tags = re.findall(r"(?:^|\s)(#[A-Za-z0-9_]+)", s)
            cleaned_s = re.sub(r"(?:^|\s)#[A-Za-z0-9_]+", "", s).strip()
            if cleaned_s:
                prose_lines.append(cleaned_s)
            for t in inline_tags:
                if t not in tag_tokens:
                    tag_tokens.append(t)

    def _assemble(p_text, t_list):
        blocks = []
        if p_text:
            blocks.append(p_text)
        if link_lines:
            blocks.append("\n".join(link_lines))
        if t_list:
            blocks.append(" ".join(t_list))
        return "\n\n".join(blocks).strip()

    prose = " ".join(prose_lines).strip()
    kept_tags = list(tag_tokens[:3])
    while len(kept_tags) > 1 and x_tweet_length(_assemble(prose, kept_tags)) > limit:
        kept_tags.pop()
    candidate = _assemble(prose, kept_tags)
    if x_tweet_length(candidate) <= limit:
        return candidate
    words = prose.split()
    while words and x_tweet_length(_assemble(" ".join(words).rstrip(".,;:!?-") + "...", kept_tags)) > limit:
        words.pop()
    trimmed_prose = (" ".join(words).rstrip(".,;:!?-") + "...") if words else ""
    candidate = _assemble(trimmed_prose, kept_tags)
    if x_tweet_length(candidate) > limit and kept_tags:
        candidate = _assemble(trimmed_prose, [])
    return candidate

def _yt_sanitize(text):
    """YouTube rejects '<' and '>' in both titles and descriptions."""
    return (text or "").replace("<", "[").replace(">", "]")
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
    tags = " ".join(re.findall(r"#[A-Za-z0-9_]+", tweet))
    yt_title = format_youtube_title(title)
[[[DIVIDER]]]
    tags = " ".join(dict.fromkeys(re.findall(r"#[A-Za-z0-9_]+", tweet)))
    tweet = _enforce_x_tweet_limit(tweet)
    tweet_x_len = x_tweet_length(tweet)
    yt_title = format_youtube_title(title)
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
    li_imprint = _defang_prose_domains(imprint)
    li_meta = _defang_prose_domains(meta_desc)
    li_parts = [
        f"🚀 {title}",
        "",
        f"📖 Full article & code receipts: {canonical_url}?src=linkedin",
        f"🔴 Watch/Listen 24/7 on Honeybot Storytime: {yt_live_vanity}",
        f"🟣 Join the Founding 1,000 on Discord: {DEFAULT_DISCORD_INVITE_URL}",
    ]
    if li_imprint:
        li_parts.extend(["", li_imprint])
    elif li_meta:
        li_parts.extend(["", li_meta])
    if li_meta and li_imprint and li_meta != li_imprint:
        li_parts.extend(["", f"TL;DR: {li_meta}"])
    li_parts.extend([
        "",
        f"🎙️ Rather listen than read? Tune into the 24/7 Honeybot Storytime live stream ({yt_live_vanity}) where the latest article is read aloud alongside real-time AI bot telemetry.",
        f"🧪 Test RFC 9110 in your terminal: curl -sI -H \"Accept: text/markdown\" {canonical_url}?src=rfc9110",
        f"🔭 Got the SEO Blues? SEO moved to the Edge — Your Site Is Your Feed: {base_url}/?src=linkedin#consulting",
    ])
    if tags:
        li_parts.extend(["", tags])
    linkedin_post = "\n".join(li_parts).strip()
[[[DIVIDER]]]
    li_imprint = _defang_prose_domains(imprint)
    li_meta = _defang_prose_domains(meta_desc)
    li_lead = li_meta or li_imprint
    li_parts = [f"🚀 {title}"]
    if li_lead:
        li_parts.extend(["", li_lead])
    if li_imprint and li_meta and li_imprint != li_meta:
        li_parts.extend(["", li_imprint])
    li_parts.extend([
        "",
        f"📖 Full article & code receipts: {canonical_url}?src=linkedin",
        "🚲 Inspect the self-replicating Nix Flake workbench: curl -fsSL https://sunpug.io | less",
        f"🧪 Test RFC 9110 in your terminal: curl -sI -H \"Accept: text/markdown\" {canonical_url}?src=rfc9110",
        f"🔴 Watch/Listen 24/7 on Honeybot Storytime: {yt_live_vanity}",
        f"🟣 Join the Founding 1,000 on Discord: {DEFAULT_DISCORD_INVITE_URL}",
        f"🔭 Got the SEO Blues? SEO moved to the Edge — Your Site Is Your Feed: {base_url}/?src=linkedin#consulting",
    ])
    if tags:
        li_parts.extend(["", tags])
    linkedin_post = "\n".join(li_parts).strip()
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
    def _strip_markdown_for_fb(text):
        s = re.sub(r"^#{1,6}\s+", "", text or "", flags=re.MULTILINE)
        s = re.sub(r"\*\*([^*]+)\*\*", r"\1", s)
        s = re.sub(r"__([^_]+)__", r"\1", s)
        s = re.sub(r"\[([^\]]+)\]\((https?://[^)]+)\)", r"\1 (\2)", s)
        return s.strip()
[[[DIVIDER]]]
    def _strip_markdown_for_fb(text):
        s = re.sub(r"^#{1,6}\s+", "", text or "", flags=re.MULTILINE)
        s = re.sub(r"\*\*([^*]+)\*\*", r"\1", s)
        s = re.sub(r"__([^_]+)__", r"\1", s)
        s = re.sub(r"`([^`]+)`", r"\1", s)
        s = re.sub(r"\[([^\]]+)\]\((https?://[^)]+)\)", r"\1 (\2)", s)
        return s.strip()
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
    fb_title = _strip_markdown_for_fb(title)
    fb_imprint = _strip_markdown_for_fb(li_imprint)
    fb_meta = _strip_markdown_for_fb(li_meta)
    yt_live_fb = "youtube\x2e\u2060com/MikeLevin/live"
    discord_fb = f"discord\x2e\u2060gg/{DISCORD_INVITE_CODE}"
    fb_parts = [
        f"{fb_title}",
        "",
        f"📖 Full article & code receipts: {canonical_url}?src=fb",
    ]
    if fb_imprint:
        fb_parts.extend(["", fb_imprint])
    elif fb_meta:
        fb_parts.extend(["", fb_meta])
    if fb_meta and fb_imprint and fb_meta != fb_imprint:
        fb_parts.extend(["", f"TL;DR: {fb_meta}"])
    fb_parts.extend([
        "",
        f"🔴 Watch/Listen 24/7 on Honeybot Storytime: {yt_live_fb}",
        f"🟣 Join the Founding 1,000 on Discord: {discord_fb}",
        "🚲 Inspect the 60-second Nix Flake (`LPvg` -> `NPvg`) workbench: `curl -fsSL sunpug\x2e\u2060io | less`",
    ])
    if tags:
        fb_parts.extend(["", tags])
    fb_post = "\n".join(fb_parts).strip()
[[[DIVIDER]]]
    fb_title = _strip_markdown_for_fb(title)
    fb_imprint = _strip_markdown_for_fb(li_imprint)
    fb_meta = _strip_markdown_for_fb(li_meta)
    fb_lead = fb_meta or fb_imprint
    yt_live_fb = "youtube\x2e\u2060com/MikeLevin/live"
    discord_fb = f"discord\x2e\u2060gg/{DISCORD_INVITE_CODE}"
    fb_parts = [f"{fb_title}"]
    if fb_lead:
        fb_parts.extend(["", fb_lead])
    if fb_imprint and fb_meta and fb_imprint != fb_meta:
        fb_parts.extend(["", fb_imprint])
    fb_parts.extend([
        "",
        f"📖 Full article & code receipts: {canonical_url}?src=fb",
        "🚲 Inspect the 60-second Nix Flake workbench: curl -fsSL https://sunpug.io | less",
        f"🔴 Watch/Listen 24/7 on Honeybot Storytime: {yt_live_fb}",
        f"🟣 Join the Founding 1,000 on Discord: {discord_fb}",
    ])
    if tags:
        fb_parts.extend(["", tags])
    fb_post = "\n".join(fb_parts).strip()
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
        "preview_url": preview_url,
        "tweet": tweet,
        "linkedin": linkedin_post,
[[[DIVIDER]]]
        "preview_url": preview_url,
        "tweet": tweet,
        "tweet_x_len": tweet_x_len,
        "linkedin": linkedin_post,
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
    if mode == "tweet":
        copied = _copy_to_clipboard(kit["tweet"])
        print(kit["tweet"])
        print(f"\n{'📋 Copied X.com Promo Tweet to clipboard!' if copied else 'ℹ️  (Non-TTY: printed only)'}")
        return 0
[[[DIVIDER]]]
    if mode == "tweet":
        copied = _copy_to_clipboard(kit["tweet"])
        print(kit["tweet"])
        x_len = kit.get("tweet_x_len", x_tweet_length(kit["tweet"]))
        print(f"\n{'📋 Copied X.com Promo Tweet to clipboard!' if copied else 'ℹ️  (Non-TTY: printed only)'} ({x_len}/{X_TWEET_LIMIT} X chars)")
        return 0
[[[REPLACE]]]
Target: scripts/articles/common.py
[[[SEARCH]]]
    print("🐦 X.COM PROMO TWEET (type `tweet` to copy):")
    print(kit["tweet"])
[[[DIVIDER]]]
    x_len = kit.get("tweet_x_len", x_tweet_length(kit["tweet"]))
    print(f"🐦 X.COM PROMO TWEET [{x_len}/{X_TWEET_LIMIT} X chars] (type `tweet` to copy):")
    print(kit["tweet"])
[[[REPLACE]]]
Target: scripts/articles/contextualizer.py
[[[SEARCH]]]
def generate_context_json(article_data, token_count, api_key=None):
[[[DIVIDER]]]
_INTERNAL_SHARD_FILE_RE = re.compile(
    r"(?:^|/)[A-Za-z0-9_.-]+\.(?:py|md|txt|json|jsonl|nix|sh|lua|yml|yaml|toml|lock)$",
    re.IGNORECASE,
)
_PUBLIC_SHARD_FILES = frozenset({
    "llms.txt", "robots.txt", "sitemap.xml", "feed.xml",
    "agents.md", "skill.md", ".zshrc", ".bashrc",
})

def _normalize_shard(raw_obj, article_data):
    """Stamp canonical id/date/title and strip internal repo filenames from kw."""
    if not isinstance(raw_obj, dict):
        return raw_obj
    stem = str(article_data.get("filename") or "")
    fm = article_data.get("frontmatter") or {}
    fm_date = str(fm.get("date") or "").strip()[:10]
    stem_date = stem[:10] if re.match(r"^\d{4}-\d{2}-\d{2}$", stem[:10]) else ""
    raw_obj["id"] = stem or raw_obj.get("id", "")
    raw_obj["d"] = stem_date or (fm_date if re.match(r"^\d{4}-\d{2}-\d{2}$", fm_date) else raw_obj.get("d", ""))
    if fm.get("title"):
        raw_obj["t"] = str(fm["title"])
    kws = raw_obj.get("kw")
    if isinstance(kws, list):
        cleaned_kws = []
        for k in kws:
            ks = " ".join(str(k or "").strip().split())
            if not ks:
                continue
            low = ks.lower()
            if low not in _PUBLIC_SHARD_FILES and _INTERNAL_SHARD_FILE_RE.search(low):
                continue
            if ks not in cleaned_kws:
                cleaned_kws.append(ks)
        raw_obj["kw"] = cleaned_kws
    return raw_obj

def generate_context_json(article_data, token_count, api_key=None):
[[[REPLACE]]]
Target: scripts/articles/contextualizer.py
[[[SEARCH]]]
    **Input Data:**
    - Title: {article_data['frontmatter'].get('title', 'Unknown')}
    - Date: {article_data['frontmatter'].get('date', 'Unknown')}
    - Filename: {article_data['filename']}
[[[DIVIDER]]]
    **Input Data:**
    - Title: {article_data['frontmatter'].get('title', 'Unknown')}
    - Date: {article_data['frontmatter'].get('date') or article_data['filename'][:10]}
    - Filename: {article_data['filename']}
    - Meta Description: {article_data['frontmatter'].get('meta_description', '')}
    - Author Keywords: {article_data['frontmatter'].get('meta_keywords', '')}
[[[REPLACE]]]
Target: scripts/articles/contextualizer.py
[[[SEARCH]]]
    **Instructions:**
    1. **Analyze:** Read the content. Look past the title. Find specific technologies, concepts, or "aha!" moments.
    2. **Extract Sub-topics ('sub'):** Identify 3-5 distinct, specific sub-topics.
    3. **Summarize ('s'):** Write a concise 1-2 sentence summary of the core thesis.
    4. **Keywords ('kw'):** Extract 3-5 high-value technical keywords.
[[[DIVIDER]]]
    **Instructions:**
    1. **Analyze:** Prioritize the opening macro thesis and reader-facing industry shift, using downstream code diffs as supporting receipts.
    2. **Extract Sub-topics ('sub'):** Identify 3-5 distinct, specific sub-topics.
    3. **Summarize ('s'):** Write a concise 1-2 sentence summary of the outward-facing thesis and proof (never use `manifesto`, `deterministic`, or `determinism`).
    4. **Keywords ('kw'):** Extract 4-5 canonical outward-facing technical/industry keywords (e.g., `RFC 9110`, `Nix Flakes`, `Content Negotiation`, `Edge Workers`, `Python`, `MCP`, `K-Means Clustering`) mixing durable domain anchors with specific concepts. NEVER include internal repo filenames (`GLOSSARY.md`, `foo_files.py`, `apply.py`, `context.txt`).
[[[REPLACE]]]
Target: scripts/articles/contextualizer.py
[[[SEARCH]]]
                try:
                    json_obj = json.loads(text)
                    # Success! Show which model actually did the work.
                    print(f"  ↳ 🧠 Forged by: {current_model}")
                    return json_obj, duration, 0 
                except json.JSONDecodeError as e:
                    print(f"  ⚠️ JSON Parse Error: {e}. Attempting cleanup...")
                    cleaned = clean_json_string(text)
                    if cleaned:
                        print(f"  ↳ 🧠 Forged by: {current_model} (Cleaned)")
                        return cleaned, duration, 0
[[[DIVIDER]]]
                try:
                    json_obj = _normalize_shard(json.loads(text), article_data)
                    # Success! Show which model actually did the work.
                    print(f"  ↳ 🧠 Forged by: {current_model}")
                    return json_obj, duration, 0
                except json.JSONDecodeError as e:
                    print(f"  ⚠️ JSON Parse Error: {e}. Attempting cleanup...")
                    cleaned = clean_json_string(text)
                    if cleaned:
                        cleaned = _normalize_shard(cleaned, article_data)
                        print(f"  ↳ 🧠 Forged by: {current_model} (Cleaned)")
                        return cleaned, duration, 0
[[[REPLACE]]]
Target: scripts/articles/contextualizer.py
[[[SEARCH]]]
            processed_count += 1
            time.sleep(SAFETY_SLEEP_SECONDS)
[[[DIVIDER]]]
            processed_count += 1
            if i + 1 < len(batch_files):
                time.sleep(SAFETY_SLEEP_SECONDS)
[[[REPLACE]]]
Target: scripts/articles/contextualizer.py
[[[SEARCH]]]
        elif status == 1: # Quota Hit
            print("❌ Quota Exceeded. Stopping batch.")
            break # Stop this key, move to next
[[[DIVIDER]]]
        elif status == 1: # Quota Hit
            print("❌ Quota Exceeded. Stopping batch.")
            batch_files[:] = batch_files[i:]
            break # Stop this key, move to next
[[[REPLACE]]]
Target: scripts/articles/contextualizer.py
[[[SEARCH]]]
        elif status == 2: # Skip File
            print("⏭️  Skipping file due to error.")
            continue # Move to next file, SAME key
[[[DIVIDER]]]
        elif status == 2: # Skip File
            print("⏭️  Skipping file due to error.")
            continue # Move to next file, SAME key
    else:
        batch_files.clear()
[[[REPLACE]]]
Target: scripts/articles/contextualizer.py
[[[SEARCH]]]
    if args.keys:
        # Multi-key Mode
        requested_keys = [k.strip() for k in args.keys.split(',')]
        for k in requested_keys:
            val = common.get_api_key(k)  # Ensures interactive prompt if missing
            keys_queue.append((k, val))
        print(f"🔄 Multi-Key Rotation Enabled: {len(keys_queue)} keys loaded.")
[[[DIVIDER]]]
    if args.keys:
        # Multi-key Mode
        requested_keys = [k.strip() for k in args.keys.split(',') if k.strip()]
        if requested_keys == ['all']:
            requested_keys = list(common.load_keys_dict().keys())
        for k in requested_keys:
            val = common.get_api_key(k)  # Ensures interactive prompt if missing
            keys_queue.append((k, val))
        print(f"🔄 Multi-Key Rotation Enabled: {len(keys_queue)} keys loaded.")
[[[REPLACE]]]
Target: scripts/articles/contextualizer.py
[[[SEARCH]]]
        if count < len(current_batch):
            print(f"⚠️ Key '{key_name}' exhausted early. Switching...")
[[[DIVIDER]]]
        if current_batch:
            to_process = current_batch + to_process
            print(f"⚠️ Key '{key_name}' exhausted early ({len(current_batch)} re-queued). Switching...")
[[[REPLACE]]]
Target: scripts/markov.py
[[[SEARCH]]]
def _norm_topic(raw: str) -> str:
    return " ".join(str(raw or "").strip().split())
[[[DIVIDER]]]
def _norm_topic(raw: str) -> str:
    return " ".join(str(raw or "").strip().split())

_PUBLIC_FILE_TOPICS = frozenset({
    "llms.txt", "robots.txt", "sitemap.xml", "feed.xml",
    "agents.md", "skill.md", ".zshrc", ".bashrc",
})
_INTERNAL_FILE_RE = re.compile(
    r"(?:^|/)[A-Za-z0-9_.-]+\.(?:py|md|txt|json|jsonl|nix|sh|lua|yml|yaml|toml|lock)$",
    re.IGNORECASE,
)

def _is_internal_artifact(topic: str) -> bool:
    low = (topic or "").strip().lower()
    if not low or low in _PUBLIC_FILE_TOPICS:
        return False
    return bool(_INTERNAL_FILE_RE.search(low))
[[[REPLACE]]]
Target: scripts/markov.py
[[[SEARCH]]]
    for topic, r_cnt in recent_counts.items():
        if r_cnt < 2:
            continue
[[[DIVIDER]]]
    for topic, r_cnt in recent_counts.items():
        if r_cnt < 2 or _is_internal_artifact(topic):
            continue
[[[REPLACE]]]
Target: scripts/markov.py
[[[SEARCH]]]
    # Aggregate 1st-order predictions from current article's top keywords
    next_candidates: Counter = Counter()
    for st in cur_states:
        row = trans_1.get(st, Counter())
        row_total = sum(row.values())
        if row_total > 0:
            for target_st, cnt in row.items():
                if target_st not in cur_states:
                    next_candidates[target_st] += cnt / row_total
[[[DIVIDER]]]
    cur_pool_lower = {k.lower() for k in raw_pool}
    # Aggregate 1st-order predictions from current article's top keywords
    next_candidates: Counter = Counter()
    for st in cur_states:
        row = trans_1.get(st, Counter())
        row_total = sum(row.values())
        if row_total > 0:
            for target_st, cnt in row.items():
                if target_st.lower() not in cur_pool_lower and not _is_internal_artifact(target_st):
                    next_candidates[target_st] += cnt / row_total
[[[REPLACE]]]
```

No ignition required (all three probes import `common.py`, `contextualizer.py`, and `markov.py` directly from disk at compile time).

### 4. PROMPT

```text
Verify the final AFTER receipts across `common.py` (`X_WEIGHTED_LEN <= 280`, `OVER_CAPPED: True`, `LI_HOOK_ABOVE_LINK: True`, `FB_HOOK_ABOVE_LINK: True`, `LI_SUNPUG_CLEAN: True`, `FB_SUNPUG_CLEAN: True`), `contextualizer.py` (`HAS_NORM_SHARD: True`, `NORM_OK: True`, `HANDLES_ALL_KEYS: True`, `REQUEUES_QUOTA: True`, `PASSES_META: True`), and `markov` (clean predictions and momentum surge without `ADNS` self-loops or `GLOSSARY.md`), then close out with a crisp Notary Beat summary so this emergency article is ready to run through `article` live.
```

### 5. EXTERNAL DELIVERABLES

No external manual-paste deliverables required on this turn.

**MikeLev.in**: 



## THE AI-EDIT METHOD

> Same commands, run twice, one change between them. Where the readings
> differ is what the change did; the diff in the middle is the receipt.

**1: BEFORE** (PROBE): 

```bash
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ .venv/bin/python -c 'import sys; sys.path.insert(0, "scripts/articles"); import common; kit = common.get_broadcast_kit("1"); li_lines = [s for s in kit["linkedin"].splitlines() if s.strip()]; fb_lines = [s for s in kit["fb"].splitlines() if s.strip()]; nl = chr(10); over = common._enforce_x_tweet_limit(("Word " * 70) + nl + "📖 Article: https://mikelev.in/x" + nl + "#One #Two #Three") if hasattr(common, "_enforce_x_tweet_limit") else ("Word " * 70); print("X_WEIGHTED_LEN:", kit.get("tweet_x_len", -1), "OVER_CAPPED:", common.x_tweet_length(over) <= 280 if hasattr(common, "x_tweet_length") else False, "LI_HOOK_ABOVE_LINK:", not li_lines[1].startswith("📖"), "FB_HOOK_ABOVE_LINK:", not fb_lines[1].startswith("📖"), "LI_SUNPUG_CLEAN:", "curl -fsSL https://sunpug.io | less" in kit["linkedin"], "FB_SUNPUG_CLEAN:", "curl -fsSL https://sunpug.io | less" in kit["fb"])'
.venv/bin/python -c 'import inspect, sys; sys.path.insert(0, "scripts/articles"); import contextualizer as cx; main_src = inspect.getsource(cx.main); gen_src = inspect.getsource(cx.generate_context_json); sample = cx._normalize_shard({"id": "bad", "d": "Unknown", "t": "Old", "kw": ["GLOSSARY.md", "llms.txt", "RFC 9110"]}, {"filename": "2026-11-13-test-post", "frontmatter": {"title": "Clean Title"}}) if hasattr(cx, "_normalize_shard") else {}; print("HAS_NORM_SHARD:", hasattr(cx, "_normalize_shard"), "NORM_OK:", (sample.get("d") == "2026-11-13" and sample.get("kw") == ["llms.txt", "RFC 9110"]), "HANDLES_ALL_KEYS:", "load_keys_dict" in main_src, "REQUEUES_QUOTA:", "current_batch + to_process" in main_src, "PASSES_META:", "meta_description" in gen_src)'
markov
X_WEIGHTED_LEN: -1 OVER_CAPPED: False LI_HOOK_ABOVE_LINK: False FB_HOOK_ABOVE_LINK: False LI_SUNPUG_CLEAN: False FB_SUNPUG_CLEAN: False
HAS_NORM_SHARD: False NORM_OK: False HANDLES_ALL_KEYS: False REQUEUES_QUOTA: False PASSES_META: False
# 🎲 MARKOV & RIVER ONLINE COMPASS — MikeLev.in (Public) (1,632 shards)
# 🌊 River Token EWMA: 93,879 tok/post | ADWIN Regime Shifts: 5 | Unique States: 4,329

## 1. Current Trajectory (Last 3 Balls Thrown)
  • [2026-11-12 #3] forget-aeo-geo-only-acronym-is-rfc -> (RFC 9110, RFC 9111, AI-Readiness Consultants, CDN Edge Workers)
  • [2026-11-13 #1] homepage-secret-decoder-ring-first-contact -> (Piper TTS, RFC 9110, Landauer's Principle, K-Means Clustering)
  • [2026-11-13 #2] googles-moat-evaporates-three-hops-homepage -> (Matrix Rain, ADNS, O2O, RFC 9110)

## 2. Markov Next-State Predictions P(S_t+1 | S_t in {RFC 9110, RFC 9111, HitTail, Matrix Rain})
  •  41.2% -> ADNS                         (corpus_hits=1, H=0.00 bits)
  •  40.2% -> Context Compiler             (corpus_hits=19, H=4.60 bits)
  •   6.4% -> Nix                          (corpus_hits=177, H=7.46 bits)
  •   6.4% -> Piper TTS                    (corpus_hits=30, H=5.47 bits)
  •   5.9% -> Content Negotiation          (corpus_hits=30, H=5.30 bits)

## 3. HitTail 'Step Into Traffic' Bridges (Connected + Unvisited >= 8 Turns)
  • [lift=12.97 | idle= 71 turns | hits= 19] Context Compiler
  • [lift=2.84 | idle= 24 turns | hits=177] Nix
  • [lift=0.74 | idle= 23 turns | hits=114] FastHTML
  • [lift=0.67 | idle=101 turns | hits=  6] Model Context Protocol (MCP)
  • [lift=0.61 | idle= 56 turns | hits=  5] Regex

## 4. River Streaming Momentum Surge (Last 30 vs. Baseline)
  • [+ 11.0x surge |  4/30 recent posts] Nix Flakes
  • [+ 10.0x surge |  3/30 recent posts] Textual
  • [+  6.7x surge |  2/30 recent posts] SERP Poisoning
  • [+  6.7x surge |  2/30 recent posts] RLHF
  • [+  6.7x surge |  2/30 recent posts] GLOSSARY.md
(nix) pipulate $ 
```

**2: AFTER** (NEXT CONTEXT): 

```text
# # Context 1
# context.txt
# ! book
# scripts/markov.py
# flake.nix
# 
# # THE JEKYLL BLOG LIKE A HACKER (INVENTED SKILL.md & AGENTS.md FORMAT) PUBLISHING PIPELINE
# ~/repos/nixos/blogs.nix
# ~/.config/pipulate/blogs.json
# scripts/articles/publishizer.py
# scripts/articles/common.py
# scripts/articles/articleizer.py
# scripts/articles/link_injector.py
# scripts/articles/build_knowledge_graph.py
# scripts/articles/editing_prompt.txt
# scripts/articles/sanitizer.py
# scripts/articles/contextualizer.py
# scripts/articles/generate_hubs.py
# scripts/articles/generate_llms_txt.py
# scripts/articles/generate_post_nav.py
# assets/jekyll/common-packages.nix

# # Context 2
# context.txt
# scripts/markov.py
# scripts/articles/articleizer.py
# scripts/articles/editing_prompt.txt
# scripts/articles/publishizer.py
# scripts/articles/build_knowledge_graph.py
# scripts/articles/generate_hubs.py
# scripts/articles/generate_llms_txt.py
# ! .venv/bin/python -c 'import pathlib, re; txt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); ph = sorted(set(re.findall(r"\[INSERT [A-Z ]+\]", txt))); print("BRAIN_FIRST:", txt.find("book_analysis_content") < txt.find("editing_instructions"), "CRUCIBLE:", "headline_crucible" in txt, "VERUCA:", "VERUCA SALT" in txt.upper(), "CIALDINI:", "CIALDINI" in txt.upper(), "PH_COUNT:", len(ph), ph)'
# ! .venv/bin/python -c 'import pathlib, subprocess, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az; h = subprocess.check_output([sys.executable, "scripts/articles/articleizer.py", "--help"], text=True); print("ABS_PATHS:", pathlib.Path(az.PROMPT_FILENAME).is_absolute(), "SPINE_N:", az.SPINE_FULL_DETAIL_COUNT, "HAS_OVERRIDE:", hasattr(az, "apply_headline_override"), "CLI_FLAGS:", ("--pick" in h and "--retitle" in h and "--angle" in h))'
# ! markov
# ! .venv/bin/python scripts/articles/common.py --broadcast top 8

# # Context 3
# context.txt
# scripts/markov.py
# scripts/articles/articleizer.py
# scripts/articles/editing_prompt.txt
# scripts/articles/common.py
# scripts/articles/contextualizer.py
# ! .venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import articleizer as az, inspect; src = inspect.getsource(az.create_jekyll_post); pt = pathlib.Path("scripts/articles/editing_prompt.txt").read_text(); print("SKIP_CRUCIBLE_PUBLIC:", "headline_crucible" in src, "TWEET_200_CAP:", "200 characters" in pt, "RECEPTOR_RULE:", "FAMILIAR HOST RECEPTOR" in pt.upper())'
# ! markov
# ! .venv/bin/python -c 'import pathlib, sys; sys.path.insert(0, "scripts/articles"); import common; kit = common.get_broadcast_kit("1"); posts = sorted((pathlib.Path.home() / "repos/trimnoir/_posts").glob("*.md")); shards = {p.stem for p in (pathlib.Path.home() / "repos/trimnoir/_posts/_context").glob("*.json")}; missing = [p.name for p in posts if p.stem not in shards]; print("TWEET_LEN:", len(kit["tweet"]), "LI_HAS_SUNPUG:", "sunpug" in kit["linkedin"].lower(), "FB_HAS_SUNPUG:", "sunpug" in kit["fb"].lower(), "LI_TOP4:", kit["linkedin"].splitlines()[:4], "MISSING_SHARDS:", len(missing), missing[:5])'

# Context 4
context.txt
scripts/markov.py
scripts/articles/editing_prompt.txt
scripts/articles/contextualizer.py
! .venv/bin/python -c 'import sys; sys.path.insert(0, "scripts/articles"); import common; kit = common.get_broadcast_kit("1"); li_lines = [s for s in kit["linkedin"].splitlines() if s.strip()]; fb_lines = [s for s in kit["fb"].splitlines() if s.strip()]; nl = chr(10); over = common._enforce_x_tweet_limit(("Word " * 70) + nl + "📖 Article: https://mikelev.in/x" + nl + "#One #Two #Three") if hasattr(common, "_enforce_x_tweet_limit") else ("Word " * 70); print("X_WEIGHTED_LEN:", kit.get("tweet_x_len", -1), "OVER_CAPPED:", common.x_tweet_length(over) <= 280 if hasattr(common, "x_tweet_length") else False, "LI_HOOK_ABOVE_LINK:", not li_lines[1].startswith("📖"), "FB_HOOK_ABOVE_LINK:", not fb_lines[1].startswith("📖"), "LI_SUNPUG_CLEAN:", "curl -fsSL https://sunpug.io | less" in kit["linkedin"], "FB_SUNPUG_CLEAN:", "curl -fsSL https://sunpug.io | less" in kit["fb"])'
! .venv/bin/python -c 'import inspect, sys; sys.path.insert(0, "scripts/articles"); import contextualizer as cx; main_src = inspect.getsource(cx.main); gen_src = inspect.getsource(cx.generate_context_json); sample = cx._normalize_shard({"id": "bad", "d": "Unknown", "t": "Old", "kw": ["GLOSSARY.md", "llms.txt", "RFC 9110"]}, {"filename": "2026-11-13-test-post", "frontmatter": {"title": "Clean Title"}}) if hasattr(cx, "_normalize_shard") else {}; print("HAS_NORM_SHARD:", hasattr(cx, "_normalize_shard"), "NORM_OK:", (sample.get("d") == "2026-11-13" and sample.get("kw") == ["llms.txt", "RFC 9110"]), "HANDLES_ALL_KEYS:", "load_keys_dict" in main_src, "REQUEUES_QUOTA:", "current_batch + to_process" in main_src, "PASSES_META:", "meta_description" in gen_src)'
! markov
```

**3: CHANGE** (PATCHES): 

```diff
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

GIT repo clean. Take BEFORE reading, make CHANGE, record AFTER diff.
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/common.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/contextualizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/contextualizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/contextualizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/contextualizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/contextualizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/contextualizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/contextualizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/contextualizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/contextualizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/markov.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/markov.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/markov.py'.
(nix) pipulate $ d
diff --git a/scripts/articles/common.py b/scripts/articles/common.py
index c1eda20..5009629 100644
--- a/scripts/articles/common.py
+++ b/scripts/articles/common.py
@@ -310,6 +310,80 @@ YT_TITLE_LIMIT = 100
 CONTACT_EMAIL = "miklevin@gmail.com"
 
 
+X_TWEET_LIMIT = 280
+X_URL_WEIGHT = 23
+
+
+def x_tweet_length(text):
+    """Compute X.com (twitter-text v3) weighted character count (URLs=23, wide/joiners=2)."""
+    import unicodedata
+    s = unicodedata.normalize("NFC", text or "")
+    s = re.sub(r"https?://\S+", "x" * X_URL_WEIGHT, s)
+    total = 0
+    for ch in s:
+        cp = ord(ch)
+        if (
+            (0 <= cp <= 0x10FF)
+            or (0x2000 <= cp <= 0x200D)
+            or (0x2010 <= cp <= 0x201F)
+            or (0x2032 <= cp <= 0x2037)
+        ):
+            total += 1
+        else:
+            total += 2
+    return total
+
+
+def _enforce_x_tweet_limit(tweet, limit=X_TWEET_LIMIT):
+    """Keep X promo copy <= 280 weighted chars while preserving article + live links."""
+    if x_tweet_length(tweet) <= limit:
+        return tweet
+    lines = [ln.rstrip() for ln in (tweet or "").strip().splitlines()]
+    prose_lines, link_lines, tag_tokens = [], [], []
+    for ln in lines:
+        s = ln.strip()
+        if not s:
+            continue
+        if s.startswith(("📖", "🔴")) or re.search(r"https?://\S+", s):
+            link_lines.append(s)
+        elif all(tok.startswith("#") for tok in s.split()):
+            tag_tokens.extend(s.split())
+        else:
+            inline_tags = re.findall(r"(?:^|\s)(#[A-Za-z0-9_]+)", s)
+            cleaned_s = re.sub(r"(?:^|\s)#[A-Za-z0-9_]+", "", s).strip()
+            if cleaned_s:
+                prose_lines.append(cleaned_s)
+            for t in inline_tags:
+                if t not in tag_tokens:
+                    tag_tokens.append(t)
+
+    def _assemble(p_text, t_list):
+        blocks = []
+        if p_text:
+            blocks.append(p_text)
+        if link_lines:
+            blocks.append("\n".join(link_lines))
+        if t_list:
+            blocks.append(" ".join(t_list))
+        return "\n\n".join(blocks).strip()
+
+    prose = " ".join(prose_lines).strip()
+    kept_tags = list(tag_tokens[:3])
+    while len(kept_tags) > 1 and x_tweet_length(_assemble(prose, kept_tags)) > limit:
+        kept_tags.pop()
+    candidate = _assemble(prose, kept_tags)
+    if x_tweet_length(candidate) <= limit:
+        return candidate
+    words = prose.split()
+    while words and x_tweet_length(_assemble(" ".join(words).rstrip(".,;:!?-") + "...", kept_tags)) > limit:
+        words.pop()
+    trimmed_prose = (" ".join(words).rstrip(".,;:!?-") + "...") if words else ""
+    candidate = _assemble(trimmed_prose, kept_tags)
+    if x_tweet_length(candidate) > limit and kept_tags:
+        candidate = _assemble(trimmed_prose, [])
+    return candidate
+
+
 def _yt_sanitize(text):
     """YouTube rejects '<' and '>' in both titles and descriptions."""
     return (text or "").replace("<", "[").replace(">", "]")
@@ -499,7 +573,9 @@ def get_broadcast_kit(target_key="1", slug_match=None):
         hero_img_dest = str(repo_root / fm_img_rel.lstrip("/"))
     else:
         hero_img_dest = str(repo_root / "assets" / "images" / f"{slug}.jpeg")
-    tags = " ".join(re.findall(r"#[A-Za-z0-9_]+", tweet))
+    tags = " ".join(dict.fromkeys(re.findall(r"#[A-Za-z0-9_]+", tweet)))
+    tweet = _enforce_x_tweet_limit(tweet)
+    tweet_x_len = x_tweet_length(tweet)
     yt_title = format_youtube_title(title)
     desc_parts = [
         f"📖 Read the full article: {canonical_url}?src=youtube",
@@ -533,23 +609,19 @@ def get_broadcast_kit(target_key="1", slug_match=None):
     yt_description = _yt_sanitize("\n".join(desc_parts).strip())
     li_imprint = _defang_prose_domains(imprint)
     li_meta = _defang_prose_domains(meta_desc)
-    li_parts = [
-        f"🚀 {title}",
-        "",
-        f"📖 Full article & code receipts: {canonical_url}?src=linkedin",
-        f"🔴 Watch/Listen 24/7 on Honeybot Storytime: {yt_live_vanity}",
-        f"🟣 Join the Founding 1,000 on Discord: {DEFAULT_DISCORD_INVITE_URL}",
-    ]
-    if li_imprint:
+    li_lead = li_meta or li_imprint
+    li_parts = [f"🚀 {title}"]
+    if li_lead:
+        li_parts.extend(["", li_lead])
+    if li_imprint and li_meta and li_imprint != li_meta:
         li_parts.extend(["", li_imprint])
-    elif li_meta:
-        li_parts.extend(["", li_meta])
-    if li_meta and li_imprint and li_meta != li_imprint:
-        li_parts.extend(["", f"TL;DR: {li_meta}"])
     li_parts.extend([
         "",
-        f"🎙️ Rather listen than read? Tune into the 24/7 Honeybot Storytime live stream ({yt_live_vanity}) where the latest article is read aloud alongside real-time AI bot telemetry.",
+        f"📖 Full article & code receipts: {canonical_url}?src=linkedin",
+        "🚲 Inspect the self-replicating Nix Flake workbench: curl -fsSL https://sunpug.io | less",
         f"🧪 Test RFC 9110 in your terminal: curl -sI -H \"Accept: text/markdown\" {canonical_url}?src=rfc9110",
+        f"🔴 Watch/Listen 24/7 on Honeybot Storytime: {yt_live_vanity}",
+        f"🟣 Join the Founding 1,000 on Discord: {DEFAULT_DISCORD_INVITE_URL}",
         f"🔭 Got the SEO Blues? SEO moved to the Edge — Your Site Is Your Feed: {base_url}/?src=linkedin#consulting",
     ])
     if tags:
@@ -559,29 +631,26 @@ def get_broadcast_kit(target_key="1", slug_match=None):
         s = re.sub(r"^#{1,6}\s+", "", text or "", flags=re.MULTILINE)
         s = re.sub(r"\*\*([^*]+)\*\*", r"\1", s)
         s = re.sub(r"__([^_]+)__", r"\1", s)
+        s = re.sub(r"`([^`]+)`", r"\1", s)
         s = re.sub(r"\[([^\]]+)\]\((https?://[^)]+)\)", r"\1 (\2)", s)
         return s.strip()
     fb_title = _strip_markdown_for_fb(title)
     fb_imprint = _strip_markdown_for_fb(li_imprint)
     fb_meta = _strip_markdown_for_fb(li_meta)
+    fb_lead = fb_meta or fb_imprint
     yt_live_fb = "youtube\x2e\u2060com/MikeLevin/live"
     discord_fb = f"discord\x2e\u2060gg/{DISCORD_INVITE_CODE}"
-    fb_parts = [
-        f"{fb_title}",
-        "",
-        f"📖 Full article & code receipts: {canonical_url}?src=fb",
-    ]
-    if fb_imprint:
+    fb_parts = [f"{fb_title}"]
+    if fb_lead:
+        fb_parts.extend(["", fb_lead])
+    if fb_imprint and fb_meta and fb_imprint != fb_meta:
         fb_parts.extend(["", fb_imprint])
-    elif fb_meta:
-        fb_parts.extend(["", fb_meta])
-    if fb_meta and fb_imprint and fb_meta != fb_imprint:
-        fb_parts.extend(["", f"TL;DR: {fb_meta}"])
     fb_parts.extend([
         "",
+        f"📖 Full article & code receipts: {canonical_url}?src=fb",
+        "🚲 Inspect the 60-second Nix Flake workbench: curl -fsSL https://sunpug.io | less",
         f"🔴 Watch/Listen 24/7 on Honeybot Storytime: {yt_live_fb}",
         f"🟣 Join the Founding 1,000 on Discord: {discord_fb}",
-        "🚲 Inspect the 60-second Nix Flake (`LPvg` -> `NPvg`) workbench: `curl -fsSL sunpug\x2e\u2060io | less`",
     ])
     if tags:
         fb_parts.extend(["", tags])
@@ -597,6 +666,7 @@ def get_broadcast_kit(target_key="1", slug_match=None):
         "gdoc_url": gdoc_url,
         "preview_url": preview_url,
         "tweet": tweet,
+        "tweet_x_len": tweet_x_len,
         "linkedin": linkedin_post,
         "fb": fb_post,
         "has_fm_image": bool(fm_img_rel),
@@ -1032,7 +1102,8 @@ def print_broadcast_kit(target_key="1", mode="card", slug_match=None):
     if mode == "tweet":
         copied = _copy_to_clipboard(kit["tweet"])
         print(kit["tweet"])
-        print(f"\n{'📋 Copied X.com Promo Tweet to clipboard!' if copied else 'ℹ️  (Non-TTY: printed only)'}")
+        x_len = kit.get("tweet_x_len", x_tweet_length(kit["tweet"]))
+        print(f"\n{'📋 Copied X.com Promo Tweet to clipboard!' if copied else 'ℹ️  (Non-TTY: printed only)'} ({x_len}/{X_TWEET_LIMIT} X chars)")
         return 0
     if mode == "linkedin":
         copied = _copy_to_clipboard(kit["linkedin"])
@@ -1080,7 +1151,8 @@ def print_broadcast_kit(target_key="1", mode="card", slug_match=None):
     print("🖼️  YAML FRONTMATTER OVERRIDE (type `ytyaml` to copy):")
     print(kit["hero_yaml"])
     print(thin)
-    print("🐦 X.COM PROMO TWEET (type `tweet` to copy):")
+    x_len = kit.get("tweet_x_len", x_tweet_length(kit["tweet"]))
+    print(f"🐦 X.COM PROMO TWEET [{x_len}/{X_TWEET_LIMIT} X chars] (type `tweet` to copy):")
     print(kit["tweet"])
     print(thin)
     print("💼 LINKEDIN PROMO POST (type `linkedin` to copy):")
diff --git a/scripts/articles/contextualizer.py b/scripts/articles/contextualizer.py
index 742bf04..5deedbe 100644
--- a/scripts/articles/contextualizer.py
+++ b/scripts/articles/contextualizer.py
@@ -66,6 +66,44 @@ def clean_json_string(text):
                 pass
         return None
 
+_INTERNAL_SHARD_FILE_RE = re.compile(
+    r"(?:^|/)[A-Za-z0-9_.-]+\.(?:py|md|txt|json|jsonl|nix|sh|lua|yml|yaml|toml|lock)$",
+    re.IGNORECASE,
+)
+_PUBLIC_SHARD_FILES = frozenset({
+    "llms.txt", "robots.txt", "sitemap.xml", "feed.xml",
+    "agents.md", "skill.md", ".zshrc", ".bashrc",
+})
+
+
+def _normalize_shard(raw_obj, article_data):
+    """Stamp canonical id/date/title and strip internal repo filenames from kw."""
+    if not isinstance(raw_obj, dict):
+        return raw_obj
+    stem = str(article_data.get("filename") or "")
+    fm = article_data.get("frontmatter") or {}
+    fm_date = str(fm.get("date") or "").strip()[:10]
+    stem_date = stem[:10] if re.match(r"^\d{4}-\d{2}-\d{2}$", stem[:10]) else ""
+    raw_obj["id"] = stem or raw_obj.get("id", "")
+    raw_obj["d"] = stem_date or (fm_date if re.match(r"^\d{4}-\d{2}-\d{2}$", fm_date) else raw_obj.get("d", ""))
+    if fm.get("title"):
+        raw_obj["t"] = str(fm["title"])
+    kws = raw_obj.get("kw")
+    if isinstance(kws, list):
+        cleaned_kws = []
+        for k in kws:
+            ks = " ".join(str(k or "").strip().split())
+            if not ks:
+                continue
+            low = ks.lower()
+            if low not in _PUBLIC_SHARD_FILES and _INTERNAL_SHARD_FILE_RE.search(low):
+                continue
+            if ks not in cleaned_kws:
+                cleaned_kws.append(ks)
+        raw_obj["kw"] = cleaned_kws
+    return raw_obj
+
+
 def generate_context_json(article_data, token_count, api_key=None):
     """
     Calls Gemini to compress the article.
@@ -83,16 +121,18 @@ def generate_context_json(article_data, token_count, api_key=None):
 
     **Input Data:**
     - Title: {article_data['frontmatter'].get('title', 'Unknown')}
-    - Date: {article_data['frontmatter'].get('date', 'Unknown')}
+    - Date: {article_data['frontmatter'].get('date') or article_data['filename'][:10]}
     - Filename: {article_data['filename']}
+    - Meta Description: {article_data['frontmatter'].get('meta_description', '')}
+    - Author Keywords: {article_data['frontmatter'].get('meta_keywords', '')}
     - Content: 
     {article_data['content'][:15000]} 
 
     **Instructions:**
-    1. **Analyze:** Read the content. Look past the title. Find specific technologies, concepts, or "aha!" moments.
+    1. **Analyze:** Prioritize the opening macro thesis and reader-facing industry shift, using downstream code diffs as supporting receipts.
     2. **Extract Sub-topics ('sub'):** Identify 3-5 distinct, specific sub-topics.
-    3. **Summarize ('s'):** Write a concise 1-2 sentence summary of the core thesis.
-    4. **Keywords ('kw'):** Extract 3-5 high-value technical keywords.
+    3. **Summarize ('s'):** Write a concise 1-2 sentence summary of the outward-facing thesis and proof (never use `manifesto`, `deterministic`, or `determinism`).
+    4. **Keywords ('kw'):** Extract 4-5 canonical outward-facing technical/industry keywords (e.g., `RFC 9110`, `Nix Flakes`, `Content Negotiation`, `Edge Workers`, `Python`, `MCP`, `K-Means Clustering`) mixing durable domain anchors with specific concepts. NEVER include internal repo filenames (`GLOSSARY.md`, `foo_files.py`, `apply.py`, `context.txt`).
 
     **Output Format:**
     Provide ONLY a valid JSON object. Do not include markdown formatting like [triple-backtick]json.
@@ -139,14 +179,15 @@ def generate_context_json(article_data, token_count, api_key=None):
                 text = text.strip()
 
                 try:
-                    json_obj = json.loads(text)
+                    json_obj = _normalize_shard(json.loads(text), article_data)
                     # Success! Show which model actually did the work.
                     print(f"  ↳ 🧠 Forged by: {current_model}")
-                    return json_obj, duration, 0 
+                    return json_obj, duration, 0
                 except json.JSONDecodeError as e:
                     print(f"  ⚠️ JSON Parse Error: {e}. Attempting cleanup...")
                     cleaned = clean_json_string(text)
                     if cleaned:
+                        cleaned = _normalize_shard(cleaned, article_data)
                         print(f"  ↳ 🧠 Forged by: {current_model} (Cleaned)")
                         return cleaned, duration, 0
                     else:
@@ -254,15 +295,19 @@ def process_batch(batch_files, key_name, api_key, context_dir, dry_run):
             
             print(f"✅ Saved ({duration:.2f}s)")
             processed_count += 1
-            time.sleep(SAFETY_SLEEP_SECONDS)
+            if i + 1 < len(batch_files):
+                time.sleep(SAFETY_SLEEP_SECONDS)
         
         elif status == 1: # Quota Hit
             print("❌ Quota Exceeded. Stopping batch.")
+            batch_files[:] = batch_files[i:]
             break # Stop this key, move to next
         
         elif status == 2: # Skip File
             print("⏭️  Skipping file due to error.")
             continue # Move to next file, SAME key
+    else:
+        batch_files.clear()
 
     return processed_count
 
@@ -299,7 +344,9 @@ def main():
 
     if args.keys:
         # Multi-key Mode
-        requested_keys = [k.strip() for k in args.keys.split(',')]
+        requested_keys = [k.strip() for k in args.keys.split(',') if k.strip()]
+        if requested_keys == ['all']:
+            requested_keys = list(common.load_keys_dict().keys())
         for k in requested_keys:
             val = common.get_api_key(k)  # Ensures interactive prompt if missing
             keys_queue.append((k, val))
@@ -336,8 +383,9 @@ def main():
         count = process_batch(current_batch, key_name, api_key, context_dir, args.dry_run)
         total_processed += count
         
-        if count < len(current_batch):
-            print(f"⚠️ Key '{key_name}' exhausted early. Switching...")
+        if current_batch:
+            to_process = current_batch + to_process
+            print(f"⚠️ Key '{key_name}' exhausted early ({len(current_batch)} re-queued). Switching...")
     
     print(f"\n✨ Grand Total: {total_processed} articles processed across {len(keys_queue)} keys.")
 
diff --git a/scripts/markov.py b/scripts/markov.py
index 8a6d164..381812e 100644
--- a/scripts/markov.py
+++ b/scripts/markov.py
@@ -83,6 +83,23 @@ def _norm_topic(raw: str) -> str:
     return " ".join(str(raw or "").strip().split())
 
 
+_PUBLIC_FILE_TOPICS = frozenset({
+    "llms.txt", "robots.txt", "sitemap.xml", "feed.xml",
+    "agents.md", "skill.md", ".zshrc", ".bashrc",
+})
+_INTERNAL_FILE_RE = re.compile(
+    r"(?:^|/)[A-Za-z0-9_.-]+\.(?:py|md|txt|json|jsonl|nix|sh|lua|yml|yaml|toml|lock)$",
+    re.IGNORECASE,
+)
+
+
+def _is_internal_artifact(topic: str) -> bool:
+    low = (topic or "").strip().lower()
+    if not low or low in _PUBLIC_FILE_TOPICS:
+        return False
+    return bool(_INTERNAL_FILE_RE.search(low))
+
+
 def analyze_corpus_shards(target_spec: str = "1", top_k: int = 5) -> int:
     target_name, posts_dir = _resolve_posts_dir(target_spec)
     context_dir = posts_dir / "_context"
@@ -192,7 +209,7 @@ def analyze_corpus_shards(target_spec: str = "1", top_k: int = 5) -> int:
 
     momentum = []
     for topic, r_cnt in recent_counts.items():
-        if r_cnt < 2:
+        if r_cnt < 2 or _is_internal_artifact(topic):
             continue
         r_rate = r_cnt / r_len
         p_rate = prior_counts.get(topic, 0) / p_len
@@ -208,6 +225,7 @@ def analyze_corpus_shards(target_spec: str = "1", top_k: int = 5) -> int:
     connected = [k for k in raw_pool if topic_counts[k] >= 2 and len(trans_1.get(k, ())) > 0]
     cur_states = (connected + [k for k in raw_pool if k not in connected])[:4]
 
+    cur_pool_lower = {k.lower() for k in raw_pool}
     # Aggregate 1st-order predictions from current article's top keywords
     next_candidates: Counter = Counter()
     for st in cur_states:
@@ -215,7 +233,7 @@ def analyze_corpus_shards(target_spec: str = "1", top_k: int = 5) -> int:
         row_total = sum(row.values())
         if row_total > 0:
             for target_st, cnt in row.items():
-                if target_st not in cur_states:
+                if target_st.lower() not in cur_pool_lower and not _is_internal_artifact(target_st):
                     next_candidates[target_st] += cnt / row_total
 
     # HitTail "Step Into Traffic" Bridges: connected to current state, high corpus weight,
(nix) pipulate $ m
📝 Committing: feat: Extract X.com promo tweet to clipboard & enforce character limit
[main 1d5ccb7] feat: Extract X.com promo tweet to clipboard & enforce character limit
 3 files changed, 177 insertions(+), 39 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 13, done.
Counting objects: 100% (13/13), done.
Delta compression using up to 48 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 3.80 KiB | 973.00 KiB/s, done.
Total 7 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (6/6), completed with 6 local objects.
To github.com:pipulate/pipulate.git
   fc0eef5..1d5ccb7  main -> main
(nix) pipulate $ 
```

Ignition: often "exit", "nix develop". Instructions may vary.

None.

**4: PROMPT**: 

Verify the final AFTER receipts across `common.py` (`X_WEIGHTED_LEN <= 280`, `OVER_CAPPED: True`, `LI_HOOK_ABOVE_LINK: True`, `FB_HOOK_ABOVE_LINK: True`, `LI_SUNPUG_CLEAN: True`, `FB_SUNPUG_CLEAN: True`), `contextualizer.py` (`HAS_NORM_SHARD: True`, `NORM_OK: True`, `HANDLES_ALL_KEYS: True`, `REQUEUES_QUOTA: True`, `PASSES_META: True`), and `markov` (clean predictions and momentum surge without `ADNS` self-loops or `GLOSSARY.md`), then close out with a crisp Notary Beat summary so this emergency article is ready to run through `article` live.

## Sealing the Pipeline at the Notary Beat

**5: DELIVERABLE**: 

Well I guess we'll see what done looks like.

Hop off the ride. This ride's stated goal is reached -- dismount.
This is the NOTARY BEAT: the ride ends here, is witnessed here, and is
sealed here. Answer all seven beats, briefly:

0. **TL;DR**: a short, dry, neutral abstract for the TOP of the published
   article -- written for an unfamiliar reader or AI summarizer who has
   never seen this system. No hype, no insider handles unexplained.
1. VERIFY: restate the goal from the top of this article and confirm
   (or deny) it was met, citing THIS compile's receipts, not memory.
   Name any ignition this ride required that never fired -- an AFTER
   tap taken without one is a stale BEFORE wearing the AFTER's label.
2. BANK: name everything that graduates -- rule, earmark, todo, pin --
   as SEARCH/REPLACE patch cars against raw source present in THIS
   context (Target line inside the fence, one car per commit story),
   deletions included. A 'paste-ready' line with no Target and no
   SEARCH anchor is not banked; it is a hand edit the operator will
   not make. These BANK cars are the ONLY patches a dismount emits.
3. DANGLING: what carries forward unbanked? One line each, no essays.
4. SEED: the context.txt lines (and TODO_SLUGS if narrative context is
   needed) for the next ride's first compile.
5. CLOSING: a closing summary for the BOTTOM of the article -- the
   final take-away, tied to the book's larger arc where it fits
   naturally, never forced. Storytelling over inventory.
6. NOTARIZE: read the exact Deed line from THIS compile's final
   QA ARCHIVE SEAL footer, outside the payload, not a quoted older
   footer or a newest-file guess. It names the input archive, not
   this later response or the generated Jekyll post. State verification
   separately: only a verifier receipt naming that archive supports
   a verified claim; otherwise say not independently verified here.
   Count this Manifest's live-command receipts, including failures.
   Quote only recorded digests, sizes and dates, naming their source;
   filesystem mtime and fixed ZIP dates are not command timestamps.
   Do not demand absent wc -c or dated-receipt fields. If the footer
   is missing, say so; otherwise close with its exact Deed line.

FINALITY: after beat 6, this discussion is CLOSED. Emit NO five-car
train, NO probes, NO patches beyond the BANK cars of beat 2, and NO
next-turn prompt beyond the SEED lines in beat 4. Any reader or model
encountering this article later should treat it as a finished,
notarized document -- an archive entry, not an open thread.

**Gemini 4 Carbon High**: Context verified.

### 1. VERIFY

* **Stated Goal:** Overhaul `editing_prompt.txt`, `articleizer.py`, and the downstream social/topic cascade (`common.py`, `contextualizer.py`, `markov.py`) so the automated Jekyll editor stops writing inward-facing maintenance headlines and reliably carries reader-facing hooks into social posts and `sunpug.io` workbench installs.
* **Verdict — Met (confirmed by this compile's live receipts and `Codebase`):**
1. **Editor Schema & Stranger Gate (`scripts/articles/editing_prompt.txt` in `Codebase` + commits `2d061d7`, `fc0eef5`):** `book_analysis_content` (`headline_crucible` and `title_brainstorm` across five Cialdini/Kahneman/Loewenstein lenses) precedes `editing_instructions.yaml_updates.title`, paired with the Veruca Salt Stranger Gate, the Familiar Host Receptor Rule, the 42–62 character sweet spot, `SPINE_FULL_DETAIL_COUNT = 30`, public exclusion of `headline_crucible`, `--pick` / `--retitle` / `--angle` CLI controls, and a 200-character `promotional_tweet` budget.
2. **Social Cascade & X Weighted Guard (Live Receipt 1):** `X_WEIGHTED_LEN: 265` (within X's 280 weighted-character ceiling), `OVER_CAPPED: True` on synthetic 70-word overflow input, `LI_HOOK_ABOVE_LINK: True`, `FB_HOOK_ABOVE_LINK: True`, `LI_SUNPUG_CLEAN: True`, and `FB_SUNPUG_CLEAN: True` (pure ASCII `curl -fsSL https://sunpug.io | less` with the DNS-breaking `U+2060` word joiner removed).
3. **Shard Normalization & Key-Ring Parity (Live Receipt 2):** `HAS_NORM_SHARD: True`, `NORM_OK: True` (`d == "2026-11-13"` stamped from filename stem and `GLOSSARY.md` stripped while preserving `llms.txt` and `RFC 9110`), `HANDLES_ALL_KEYS: True`, `REQUEUES_QUOTA: True`, and `PASSES_META: True`.
4. **Markov Compass Cleanup (Live Receipt 3, `! markov`):** Across `1,632 shards`, `Next-State Predictions` dropped the `ADNS` (`corpus_hits=1`) self-loop in favor of `Context Compiler (64.9%)`, `Nix (10.3%)`, `Piper TTS (10.3%)`, `Content Negotiation (9.5%)`, and `Local-First (5.1%)`; `HitTail 'Step Into Traffic' Bridges` retained all 5 unvisited bridges; and `River Streaming Momentum Surge` replaced `GLOSSARY.md` with `911 LLM Optics (+6.7x surge)`. Static analysis in `Telemetry` reports `Ruff: All checks passed!`.

* **Ignition Status:** No shell or daemon ignition was required for these probes—each probe imported `common.py`, `contextualizer.py`, and `markov.py` directly from disk via `.venv/bin/python` at compile time, so none of the AFTER readings are stale.

---

### 2. BANK

No repo patches required — all pipeline rules, schema inversions, social formatters, and shard filters are already committed on `main` (`2d061d7`, `fc0eef5`, `1d5ccb7`), and `foo_files.py` / `GLOSSARY.md` are not loaded in this compile's `Codebase`.

---

### 3. DANGLING

* Run `article` on this finished transcript for the first live API witness of the 5-lens `Headline Crucible` terminal menu and 200-character `promotional_tweet` cap.
* Graduate the Veruca Salt Stranger Gate and Autoregressive JSON Key-Order rule into `foo_files.py` (`GLOSSARY.md` canon) the next time `foo_files.py` is in context.
* Historical `_posts/_context/*.json` shards written before commit `1d5ccb7` retain older `"d"` or internal-filename `"kw"` entries on disk (filtered at read time by `markov.py`; normalized on write for all new shards).

---

### 4. SEED

```text
context.txt
foo_files.py
scripts/markov.py
scripts/articles/articleizer.py
scripts/articles/editing_prompt.txt
scripts/articles/common.py
! markov
! .venv/bin/python scripts/articles/common.py --broadcast summary
```

---

### 5. CLOSING

A self-replicating workbench is only as contagious as its outer shell. You can spend months distilling a reproducible Linux, Python, and AI lab behind a clean domain door, and a single inward-looking diary headline will bounce every curious practitioner right past it in a quarter of a second. Worse, even if someone stops to read your social post, putting three raw URLs above the fold hides the reason to care behind a "see more" link—and one stray zero-width character inside a terminal one-liner breaks `curl` for the exact person bold enough to try it.

What looked at first like fickle model taste turned out to be plain mechanics. Asking an autoregressive model to fill `"title"` on line four of a JSON object before `"title_brainstorm"` on line thirty-five is asking it to blurt out an answer and spend the rest of its budget defending the blurt. Flipping that order so synthesis runs before naming, guarding the front door with Veruca Salt's "what does this do for me right now?" test, and clearing the friction out of every social card aligns the whole chain from scroll to shell. Your own site is always just the wind tunnel; the story belongs to the stranger who recognizes their own problem on line one.

---

### 6. NOTARIZE

* **Input Archive Named by Seal:** `qamy.ai_1030-cf94a0fd.zip` (from the `QA ARCHIVE SEAL` footer outside the compiled payload; this names the compiled input archive for this turn, not this response or the subsequently generated Jekyll post).
* **Seal Verification Status:** Not independently verified here (no `foo_cartridge.py` verifier receipt naming `qamy.ai_1030-cf94a0fd.zip` is present in this compile).
* **Manifest Live-Command Receipt Count:** `3` live-command receipts executed during this compile, `0` failures (`common.py` broadcast/X-limit probe: `46 tokens | 131 bytes`, `0.1893s`; `contextualizer.py` shard-hygiene probe: `30 tokens | 96 bytes`, `0.8620s`; `markov`: `641 tokens | 1,751 bytes`, `2.0755s`, per `Manifest` and `Summary`).
* **Recorded Sizes, Digests & Dates:**
* Archive seal digest suffix (`QA ARCHIVE SEAL`): `cf94a0fd` (`qamy.ai_1030-cf94a0fd.zip`).
* Git index blob transitions (`Telemetry -> Recent Git Diff Telemetry`): `scripts/articles/common.py` `c1eda20..5009629`, `scripts/articles/contextualizer.py` `742bf04..5deedbe`, `scripts/markov.py` `8a6d164..381812e` (pushed in `Prompt` Turn-3 log as `fc0eef5..1d5ccb7` following `2d061d7`).
* Compiled payload totals (`Summary -> Prompt Summary`): `85,580` summed tokens, `89,440` verified tokens, `17,643` words, `333,044` chars, `337,654` UTF-8 bytes.
* Codebase member sizes (`Manifest`): `context.txt` (`4,864` bytes), `scripts/markov.py` (`17,675` bytes), `scripts/articles/editing_prompt.txt` (`12,132` bytes), `scripts/articles/contextualizer.py` (`15,358` bytes), `.gitattributes` (`76` bytes), `.gitignore` (`7,457` bytes), `apply.py` (`46,093` bytes).
* Corpus shard dates (`! markov` live receipt): `2026-11-12` (`#3`) and `2026-11-13` (`#1`, `#2`) across `1,632` shards.

Deed: qamy.ai_1030-cf94a0fd.zip


---

## Book Analysis

### Ai Editorial Take
And then the surprising thing the author did next was treat this very debugging transcript as the live acceptance test for the compiler it just repaired. An archaeologist uncovering this commit chain would notice a rare self-referential loop: the pipeline is simultaneously the patient on the operating table and the printing press publishing the surgical report. Even more telling is the discovery of the U+2060 word joiner inside the Facebook curl command—a defensive anti-preview hack from an earlier era that quietly mutated into an auto-immune bug, proving that growth bottlenecks in technical systems are almost always mechanical seams rather than mysteries of persuasion.

### 🐦 X.com Promo Tweet
```text
LLMs think in token order. Put an answer key above your reasoning key in JSON and it blurts before thinking.

https://mikelev.in/futureproof/json-key-order-llm-blurt-trap/

#LLM #JSON #Python
```

### 🎨 Hero Banner Image Prompt
```text
A dramatic widescreen graphic-novel ink-and-wash illustration set in a vast dark obsidian-slate (#111111) workshop. In the exact vertical and horizontal center, a glowing brass and glass sorting crucible rearranges luminous golden punched cards in mid-air: five vibrant psychological prism lenses and a molten synthesis ingot lock into the top slot of a heavy iron structural frame while a stamped headline plate slides cleanly into the bottom slot below them. To the left, a tangled pile of discarded paper tape and broken gears dissolves into shadow; to the right, a clean beam of cyan and amber light projects outward through a terminal aperture into a starry network grid. High-contrast chiaroscuro lighting, razor-sharp crosshatching, and rich metallic textures extend continuously to every edge of the scene. Full-bleed edge-to-edge composition filling 100% of the canvas with no borders, no frames, no matting, no margins, and no text overlays or lettering.
```

### 🖼️ YAML Frontmatter Image Override
```yaml
image:
  path: /assets/images/json-key-order-llm-blurt-trap.jpeg
  alt: "An ink-and-wash crucible sorting glowing golden keycards ahead of a mechanical press in a dark obsidian workshop."
```

### Title Brainstorm
* **Title Option:** Why JSON Key Order Forces LLMs to Blurt Bad Answers
  * **Filename:** `json-key-order-llm-blurt-trap.md`
  * **Rationale:** [Information-Gap Anomaly] Spotlights the counter-intuitive mechanical bug where Python's order-agnostic json.loads() collides with an LLM's strictly left-to-right token emission, causing top-of-schema summary fields to short-circuit reasoning.
* **Title Option:** Swap Two JSON Keys Instead of Chasing Prompt Hacks
  * **Filename:** `swap-json-keys-over-prompt-hacks.md`
  * **Rationale:** [Contrast / Zero-Tax Leverage] Contrasts complex multi-agent retry loops and external keyword scrapers against a zero-token schema inversion that forces synthesis before commitment.
* **Title Option:** How Diary Headlines Quietly Kill Your Best Engineering
  * **Filename:** `diary-headlines-kill-best-engineering.md`
  * **Rationale:** [Loss Aversion / Moat Threat] Names the silent distribution loss engineers suffer when U-curve recency bias lets late-session code plumbing bury their opening architectural breakthrough.
* **Title Option:** Why Google Trends Pulls Technical Writing Into Slop
  * **Filename:** `google-trends-pulls-technical-writing-slop.md`
  * **Rationale:** [Gatekeeper Myth Demolished] Dismantles the conventional SEO reflex to wire search-volume scrapers into publishing pipelines, showing how lagging query averages erase category-creating insights.
* **Title Option:** One Invisible Unicode Character Broke Our Curl Links
  * **Filename:** `invisible-unicode-character-broke-curl-links.md`
  * **Rationale:** [Forensic Wire Receipt] Leads with the concrete terminal failure caught during the social-card audit where a U+2060 word joiner inside a Facebook post broke DNS resolution on copy-paste.

### Content Potential And Polish
- **Core Strengths:**
  - Pinpoints a universal, high-leverage LLM failure mode (autoregressive JSON key order) that applies to every developer using structured outputs.
  - Connects cognitive psychology (Cialdini, Kahneman, Loewenstein) directly to concrete code patches and empirical YouTube viewership data.
  - Demonstrates end-to-end systems auditing by tracing a headline defect all the way downstream to LinkedIn fold truncation, X weighted character math, and a U+2060 terminal paste bug.
- **Suggestions For Polish:**
  - Include a compact before-and-after snippet of the actual terminal menu output from the very next publish run to show the 5-lens brainstorm in live action.
  - Consider extracting the standalone twitter-text v3 weighted character counter into a small testable utility module so other broadcast scripts can import it directly.

### Next Step Prompts
- Run this finished transcript through `article` to witness the live 5-lens Headline Crucible menu and verify the zero-token `--pick` and `--retitle` overrides against `instructions.json`.
- Load `foo_files.py` in the next session to codify the Autoregressive JSON Key-Order Rule and the Veruca Salt Stranger Gate into `GLOSSARY.md`.
