Fixing Google Doc Titles: Automated Pipeline Repairs in the Age of AI

🤖 Read Raw Markdown📄 Google Doc (Try: Tools/Audio/Listen to document summary)

Setting the Stage: Context for the Curious Book Reader

In the Age of AI, maintaining structural fidelity across disparate publishing platforms—from Markdown files to Google Docs—is an important challenge. This essay explores how a minor rendering adjustment to a publishing pipeline ensures that remote documents inherit their correct frontmatter titles rather than default body headings, offering an interesting look at artifact-driven automation and script maintenance.

TL;DR: A short entry fixing one defect in a publishing pipeline that mirrors Jekyll blog posts into Google Docs. The Doc’s filename carried the article title, but its body began at the first H2 in the markdown (“Setting the Stage…”) because the title lives in YAML frontmatter, which the converter strips. The fix prepends an <h1> of the title to the HTML that Drive imports. The one-time catch-up across ~1,440 existing Docs needed no new script: the tool’s existing --force flag already re-renders every document, and its own help text names a rendering-pipeline change as the reason it exists. The change was bracketed by the same export command run before and after, with the diff and commit in between.


Technical Journal Entry Begins

🔗 Verified Pipulate Commits:

MikeLev.in: I need the article headline at the top of each Google Doc that’s created from my articles. What’s worse is I have to do a 1-time batch processing catch-up to… to what? Upsert a Google Doc? I’m not sure of the terminology here, but because I’m changing the template of what gets written into a Google Doc, I have one very big catch-up process to do which I’m unlikely to ever do again and if I do I’ll just generate the script to do it once, so unless you put however we do this catch-up directly into googledocizer.py then we want to work on a file in /tmp because I don’t want one-off scripts like that polluting the Pipulate repo.

Does this make sense? Every article when it ends up in Google Docs gets the right filename which should also be the opening headline of the article but instead it’s always something like “Setting the Stage: Context for the Curious Book Reader”.

Does the request make sense? Things are perfect the way they are except for this one detail so after we’re doing doing what we need to do as a consequence of this article and discussion, all the already generated Google Docs will be updated with the new format, and all new ones created at time of publish [Message] will get the new format moving forward.

Why the Doc Opens With Setting the Stage

Fable 5.1 Medium: Context verified. (lsa.py and config.py are imported by sibling scripts but not by googledocizer.py, so they’re irrelevant to this request.)

The request makes sense, and it’s smaller than you think

Why the Doc opens with “Setting the Stage”: frontmatter.load(md_file).content hands markdown_to_html the body only. The title lives in YAML, which is stripped. The first heading in the body is the ## Setting the Stage… H2 that articleizer prepends, so that’s what Drive shows at the top. The Doc’s filename gets the title (via _target_title), but the body never did.

The fix: prepend an <h1> of the article title to the HTML before upload. Drive’s import maps <h1> to Heading 1, so it lands as a real styled heading at the top and in the Docs outline. I’m using the bare frontmatter title (the “article headline”), not the dated 2026-09-03 (2) | … filename contract. If you’d rather the H1 match the filename exactly, it’s a one-token change: headline=target_title.

The Catch-Up Needs No Temporary Script

The catch-up needs no /tmp script. googledocizer.py already has the batch path: --force reopens the upload branch for every doc the freshness gate would otherwise skip, and its own help text says it exists for “a rendering-pipeline change” — which is exactly this. The earmark comment calls --force the wrong-sized tool for a stamp-string repair; for a template change it is the right-sized tool, because every one of the ~1,440 bodies genuinely must be re-rendered. So the one-time catch-up is:

.venv/bin/python scripts/articles/googledocizer.py -t 1 --yes --force

Only target 1 has a gdrive_folder_id, so nothing else is touched. At 0.2s pacing plus API time, budget roughly 20–40 minutes in a real terminal. One caveat: if it dies mid-sweep, a plain re-run won’t pick up the remainder (the not-yet-redone docs are still “fresh” by mtime), so you’d re-run --force from the top, or hit the stragglers with --file. After the sweep, every remote modifiedTime is newer than local, so the freshness gate goes quiet again and publish only touches new articles.

Probes and Verification Steps

Cheapest falsifying probe: export the latest Doc back as markdown and look at its first lines. Before the patch it should open with ## Setting the Stage…; after patch + --yes --latest --force it should open with # <title>.

Implementing the Headline Patch


(1) PROBES

.venv/bin/python scripts/articles/googledocizer.py "$(.venv/bin/python -c 'import json,re;p=json.load(open("/home/mike/.config/pipulate/last_published.json"))["1"]["path"];print(re.search(r"/document/d/([\w-]+)",open(p).read()).group(1))')" | head -8

This resolves the last-published post’s stamped Doc ID and fetches the Doc as markdown. It proves the current first heading is the “Setting the Stage” H2 (BEFORE) and, echoed after the patch and a --latest --force upsert, that the title H1 now leads (AFTER). Read-only.

(2) NEXT CONTEXT

scripts/articles/googledocizer.py
scripts/articles/common.py
! .venv/bin/python scripts/articles/googledocizer.py "$(.venv/bin/python -c 'import json,re;p=json.load(open("/home/mike/.config/pipulate/last_published.json"))["1"]["path"];print(re.search(r"/document/d/([\w-]+)",open(p).read()).group(1))')" | head -8

(3) PATCHES

Three cars, one file. No ignition required: the script is loaded fresh at call time.

Target: scripts/articles/googledocizer.py
[[[SEARCH]]]
import argparse
from datetime import datetime, timezone
[[[DIVIDER]]]
import argparse
from html import escape as html_escape
from datetime import datetime, timezone
[[[REPLACE]]]

Target: scripts/articles/googledocizer.py
[[[SEARCH]]]
def markdown_to_html(md_text: str) -> bytes:
    """Markdown -> minimal HTML document, ready for Drive import-on-upload."""
    if md_lib is None:
        raise RuntimeError(
            "The 'markdown' package is required. Add it to requirements.in "
            "and reinstall (probe: .venv/bin/python -c 'import markdown')."
        )
    body = md_lib.markdown(_prepare_markdown(md_text), extensions=['extra', 'sane_lists'])
    html = f"<html><head><meta charset=\"utf-8\"></head><body>{body}</body></html>"
    return html.encode('utf-8')
[[[DIVIDER]]]
def markdown_to_html(md_text: str, headline: str = "") -> bytes:
    """Markdown -> minimal HTML document, ready for Drive import-on-upload.
    `headline` is the article title, emitted as the Doc's opening <h1>. The
    title lives in YAML frontmatter, which frontmatter.load() strips from
    .content, so without this the Doc body opened on the first H2 the body
    happened to carry ("Setting the Stage: Context for the Curious Book
    Reader") while only the Drive FILENAME knew the real title. Drive's
    import maps <h1> to Heading 1, so it lands styled and in the outline.
    """
    if md_lib is None:
        raise RuntimeError(
            "The 'markdown' package is required. Add it to requirements.in "
            "and reinstall (probe: .venv/bin/python -c 'import markdown')."
        )
    body = md_lib.markdown(_prepare_markdown(md_text), extensions=['extra', 'sane_lists'])
    if headline:
        body = f"<h1>{html_escape(headline)}</h1>\n{body}"
    html = f"<html><head><meta charset=\"utf-8\"></head><body>{body}</body></html>"
    return html.encode('utf-8')
[[[REPLACE]]]

Target: scripts/articles/googledocizer.py
[[[SEARCH]]]
def _target_title(md_file: Path, post) -> str:
    metadata = post.metadata or {}
    title = _sanitize_internal_pii(
        _metadata_value(metadata, "title") or _fallback_title(md_file)
    )
    sort_order = _metadata_value(metadata, "sort_order", "order", "sort", "ordinal")
[[[DIVIDER]]]
def _headline(md_file: Path, post) -> str:
    """The bare, PII-scrubbed article title: the Doc's opening H1."""
    metadata = post.metadata or {}
    return _sanitize_internal_pii(
        _metadata_value(metadata, "title") or _fallback_title(md_file)
    )
def _target_title(md_file: Path, post) -> str:
    metadata = post.metadata or {}
    title = _headline(md_file, post)
    sort_order = _metadata_value(metadata, "sort_order", "order", "sort", "ordinal")
[[[REPLACE]]]

Target: scripts/articles/googledocizer.py
[[[SEARCH]]]
            html_bytes = markdown_to_html(frontmatter.load(md_file).content)
[[[DIVIDER]]]
            post = frontmatter.load(md_file)
            html_bytes = markdown_to_html(post.content, headline=_headline(md_file, post))
[[[REPLACE]]]

Then the smoke on one doc, and after you’ve eyeballed it, the sweep:

.venv/bin/python scripts/articles/googledocizer.py -t 1 --yes --latest --force
.venv/bin/python scripts/articles/googledocizer.py -t 1 --yes --force

(4) PROMPT

The H1 headline patch landed in googledocizer.py. The `!` receipt above is the AFTER reading of the latest Doc's export. Confirm the first heading is now `# <title>` rather than `## Setting the Stage`, and confirm the 🏁 counters from the --force sweep (Updated should be ~1,440, Failed 0, Skipped 0). If the H1 is present but you think it should carry the dated `YYYY-MM-DD (n) | title` filename form instead, say so and I'll swap the headline argument.

(5) EXTERNAL DELIVERABLES

None.

MikeLev.in:

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: Probe (BEFORE: hand-run, nothing changed yet):

$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ .venv/bin/python scripts/articles/googledocizer.py "$(.venv/bin/python -c 'import json,re;p=json.load(open("/home/mike/.config/pipulate/last_published.json"))["1"]["path"];print(re.search(r"/document/d/([\w-]+)",open(p).read()).group(1))')" | head -8
# Google Drive: 2026-09-03 (2) | The Scientific Method of Vibe-Coding: Engineering Replayable AI Workflows (application/vnd.google-apps.document) exported as text/markdown

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

In the Age of AI, relying solely on browser interfaces for quality assurance leaves developers exposed to unverified output and hidden drift. This essay explores how adopting plain text commands and rigorous, inspectable scientific methods transforms AI-assisted development into a checkable, replayable discipline, bridging the gap between raw iteration and trustworthy software delivery.  
**TL;DR**: A journal entry arguing that plain text commands, not browser clicks, are the only way to verify what an AI did on your behalf, because only a command can be replayed with the same endpoint and parameters. It reads a published article's frontmatter, headings, and metadata against an editing rule added the day before and finds the rule held on its first use, with two earlier articles as the control. It then edits the Neovim template that structures every entry in the series so the before/after/change shape is stated in one sentence, explains why an indented code block is used to quote a template that contains code fences, and rules that a client-facing verification summary should be derived from two existing artifacts rather than written by hand. The summary itself is left for a later entry.

## ---
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
BrokenPipeError: [Errno 32] Broken pipe
(nix) pipulate $ 

2: Context (AFTER: the same probes re-run by the compiler as ! lines):

# adhoc.txt    _   _   _ to set context____ _   _  ___  ____  _   Simpson Couch Gag Here (explain anything to the audience you feel needs it explained)G
#     / \   __| | | | | | ___   ___   / ___| | | |/ _ \|  _ \| |  
# ahe/ _ \ / _` | | |_| |/ _ \ / __| | |   | |_| | | | | |_) | |  Better GDocs
# ahc ___ \ (_| | |  _  | (_) | (__  | |___|  _  | |_| |  __/|_|  Easier than I thought? That's good news.
#  /_/   \_\__,_| |_| |_|\___/ \___|  \____|_| |_|\___/|_|   (_)  
# Ad Hoc CHOP: The Not-Managed-by-Git Safe-for-Client-Data place  

# OPTIONAL BUT BIG FOR FULL CONTEXT-WINDOW STORYTELLING
# ! python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs  # <-- The "Rolling Pin" that gives the 40K foot book-spine view of book-ore.
# GLOSSARY.md                 # <-- I think this glossary goes well with the book-ore spine to do world building.
# scripts/articles/lsa.py     # <-- Useful for refining commands like `posts`, critical to Second Brain concept.
# ~/repos/nixos/autognome.py  # <-- Letting the AIs really understand my environment (The Brave Little Tailor punches above Their Weight Class proving the dunning-kruger effect the gate-keeper's (lower-case) lament.)
# init.lua                    # <-- Daily driver hot-keys that overlap with aliases in flake.nix
 
# STILL BIG BUT LESS OPTIONAL (especially flake.nix)
# flake.nix                   # <-- THE ONE BIG THING TO INCLUDE Infrastructure as Code (IaC) tells LLM about your system down to the metal
# prompt_foo.py               # <-- This very content-compiling system
# foo_files.py                # <-- This is the router, evolving book outline and the things you pin-up to produced the recursive self-improvement loops

# TINY ILLUMINATING (OK to include every time / automatically = `apply.py`, `.gitignore`, `.gitattributes`)
# requirements.in             # <-- All known dependencies and (necessary) version pinning. WORA gotcha's exposed.
# __init__.py                 # <-- Master versioning
# pyproject.toml              # <-- The PyPI Packaging details

# OPTIONAL ACTUATORS (cheap and good to include to expand the AI's capabilities)
# cli.py                      # <-- Catch-all actuator for PyPI envs, Python anchoring, MCP tool-call (plus alternatives) and **kwargs like wrapping for CLI
# scripts/xp.py               # <-- Transforms host OS copy-paste buffer player-piano music into context-payload.
# scripts/ai.py               # <-- How I constantly use local AI to write git commit messages with `m` alias.
# scripts/crawl.py            # <-- Feel free to ask for something to be crawled and included in the next turn.
# scripts/weblogin.py         # <-- Lets the user "warm up" the cache for their web logins at their leisure on a profile that persists.
 
# MISCELLANEOUS (rare to include but sometimes critical)
# scripts/foo_cartridge.py    # Needs description
# scripts/foo_replay.py       # Needs description
# release.py                  # <-- How everything ends up where it does (GitHub, PyPI, etc.)
# imports/voice_synthesis.py  # <-- The wand can talk to you
# imports/ascii_displays.py   # <-- Where all the ASCII Art lives
# scripts/release/version_sync.py  # <-- Needs to be wrapped into release.py and eliminated, I think.

#                         --- Under this line is were you paste what the AI gives you ---
#                         --- We call it context but it's really just the right-hand  ---
#                         --- blast-radius of the "probes" to make this all science.  ---

# --- END `adhoc.txt` TEMPLATE ---

# STICKBUG & MOTHER CAT KATA (WORKING ON THE CHAPTER)

# assets/installer/mck.sh
# assets/installer/replay.sh

# scripts/walk.py
# scripts/walk_cartridge.py
# scripts/walk_compile.py

# assets/trails/first_context.yaml
# assets/trails/practice.yaml
# assets/trails/public_walk.yaml
# assets/trails/botify_pageworkers.yaml

# scripts/connectors/README.md
# scripts/connectors/botify.py
# scripts/connectors/confluence.py
# scripts/connectors/gmail.py
# scripts/connectors/gsc.py
# scripts/connectors/jira.py
# scripts/connectors/mcp.py
# scripts/connectors/mcp_warm.py
# scripts/connectors/sheets.py
# scripts/connectors/slack.py
# scripts/connectors/wallet.py

# scripts/bookmark_import.py
# scripts/boot_menu.py
# scripts/mother_cat.py
# scripts/sources_menu.py
# scripts/weblogin.py
# tools/scraper_tools.py

# --- START THIS DISCUSSION ---

# CONTEXT: 1
# ~/.config/pipulate/blogs.json                # <-- CAUTION! Derived from ~/repos/nixos/blogs.nix
# scripts/articles/publishizer.py              # <-- Orchestrates different publishing workflows per target blog.
# scripts/articles/common.py                   # <-- Self-explanatory
# scripts/articles/articleizer.py              # <-- Transforms raw article.txt to formal Jekyll markdown format
# scripts/articles/editing_prompt.txt          # <-- Forcing response into strict JSON data structure
# scripts/articles/sanitizer.py                # <-- Scrubs PII
# scripts/articles/gsc_historical_fetch.py
# scripts/articles/contextualizer.py           # <-- Builds JSON summaries of articles in `_posts/context/` called "Holographic Shards".
# scripts/articles/confluenceizer.py           # <-- Idempotent Jekyll-to-Confluence corporate wiki
# scripts/articles/googledocizer.py            # <-- Just added
# scripts/articles/build_knowledge_graph.py    # <-- Topically load-balances site using hierarchical K-Means keyword clustering groups
# scripts/articles/generate_ai_context.py      # <-- AIs WILL interrogate your repo. This gives epic context of article URLs for drill-down.
# scripts/articles/generate_hubs.py            # <-- Uses just-produced link-graph data to generate each of the new hubs it suggests
# scripts/articles/generate_llms_txt.py        # <-- Builds an llms.txt based on the auto-organized structure suggested here
# scripts/articles/generate_redirects.py       # <-- Generates redirect map above hub-churn suggests is needed
# scripts/articles/sanitize_redirects.py       # <-- Deals with follow-up meticulous pedantic detail required for a good Nginx redirect map

# CONTEXT: 2
scripts/articles/googledocizer.py
scripts/articles/common.py
! .venv/bin/python scripts/articles/googledocizer.py "$(.venv/bin/python -c 'import json,re;p=json.load(open("/home/mike/.config/pipulate/last_published.json"))["1"]["path"];print(re.search(r"/document/d/([\w-]+)",open(p).read()).group(1))')" | head -8
/home/mike/repos/trimnoir/_posts/2026-09-03-text-commands-the-mouse-for-ai.md  # [Idx: 1439 | Order: 1 | Tokens: 32,808 | Bytes: 142,390]
/home/mike/repos/trimnoir/_posts/2026-09-03-scientific-method-vibe-coding-replayable-workflows.md  # [Idx: 1440 | Order: 2 | Tokens: 18,245 | Bytes: 70,173]

3: Patches (the one change between the readings):

Blast Radius Check to establish bisection Left-hand Causal Boundary. It is a Popper-thing. Science.
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/googledocizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/googledocizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/googledocizer.py'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'scripts/articles/googledocizer.py'.
(nix) pipulate $ d
diff --git a/scripts/articles/googledocizer.py b/scripts/articles/googledocizer.py
index bf440049..0113372a 100644
--- a/scripts/articles/googledocizer.py
+++ b/scripts/articles/googledocizer.py
@@ -49,6 +49,7 @@ import sys
 import json
 import time
 import argparse
+from html import escape as html_escape
 from datetime import datetime, timezone
 from pathlib import Path
 
@@ -172,14 +173,23 @@ def _prepare_markdown(md_text: str) -> str:
     return _sanitize_internal_pii(md_text)
 
 
-def markdown_to_html(md_text: str) -> bytes:
-    """Markdown -> minimal HTML document, ready for Drive import-on-upload."""
+def markdown_to_html(md_text: str, headline: str = "") -> bytes:
+    """Markdown -> minimal HTML document, ready for Drive import-on-upload.
+    `headline` is the article title, emitted as the Doc's opening <h1>. The
+    title lives in YAML frontmatter, which frontmatter.load() strips from
+    .content, so without this the Doc body opened on the first H2 the body
+    happened to carry ("Setting the Stage: Context for the Curious Book
+    Reader") while only the Drive FILENAME knew the real title. Drive's
+    import maps <h1> to Heading 1, so it lands styled and in the outline.
+    """
     if md_lib is None:
         raise RuntimeError(
             "The 'markdown' package is required. Add it to requirements.in "
             "and reinstall (probe: .venv/bin/python -c 'import markdown')."
         )
     body = md_lib.markdown(_prepare_markdown(md_text), extensions=['extra', 'sane_lists'])
+    if headline:
+        body = f"<h1>{html_escape(headline)}</h1>\n{body}"
     html = f"<html><head><meta charset=\"utf-8\"></head><body>{body}</body></html>"
     return html.encode('utf-8')
 
@@ -210,11 +220,15 @@ def _fallback_title(md_file: Path) -> str:
     return stem.replace("-", " ").strip().title()
 
 
-def _target_title(md_file: Path, post) -> str:
+def _headline(md_file: Path, post) -> str:
+    """The bare, PII-scrubbed article title: the Doc's opening H1."""
     metadata = post.metadata or {}
-    title = _sanitize_internal_pii(
+    return _sanitize_internal_pii(
         _metadata_value(metadata, "title") or _fallback_title(md_file)
     )
+def _target_title(md_file: Path, post) -> str:
+    metadata = post.metadata or {}
+    title = _headline(md_file, post)
     sort_order = _metadata_value(metadata, "sort_order", "order", "sort", "ordinal")
     date_part = _doc_date(md_file, metadata)
     if sort_order is None:
@@ -696,7 +710,8 @@ def main():
             # give back most of the memory win. One extra read of ~119 files
             # is cheaper than 1,431 retained bodies. Safe against the stamp
             # writer below, which only touches the file AFTER the upload.
-            html_bytes = markdown_to_html(frontmatter.load(md_file).content)
+            post = frontmatter.load(md_file)
+            html_bytes = markdown_to_html(post.content, headline=_headline(md_file, post))
             file_id, verb = drive_convert_upsert(
                 service, folder_id, target_title, html_bytes,
                 'text/html', DOC_MIME, existing_id=existing
(nix) pipulate $ m
📝 Committing: chore: Improve markdown_to_html function with headline support
[main 649f9b0f] chore: Improve markdown_to_html function with headline support
 1 file changed, 20 insertions(+), 5 deletions(-)
(nix) pipulate $ git push
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 48 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 955 bytes | 955.00 KiB/s, done.
Total 5 (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
   e06505a6..649f9b0f  main -> main
(nix) pipulate $

Ignition (what makes the patched code run before the AFTER reading – <F2>, nix develop, a re-ride – or none required):

Ha ha, I ran both together so the “smoke one doc” never really happened. I just jumped into batch processing them all! But they look great, so I’m just going to let it run all the way. See, from the “Probes” step I got very used to pasting stacked commands into the terminal to execute which works great for probes (I saw to it) but not so much in situations like this where the human was expected to run the commands one at a time. No harm and I feel much better about these documents now. I will still be able to go take my nap because this is going to take awhile to process.

4: Prompt (the next question, written before the AFTER reading is in):

The H1 headline patch landed in googledocizer.py. The ! receipt above is the AFTER reading of the latest Doc’s export. Confirm the first heading is now # <title> rather than ## Setting the Stage, and confirm the 🏁 counters from the –force sweep (Updated should be ~1,440, Failed 0, Skipped 0). If the H1 is present but you think it should carry the dated YYYY-MM-DD (n) | title filename form instead, say so and I’ll swap the headline argument.

5: Deliverables: Being delivered as we speak.

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:

  1. 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.
  2. 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.
  3. BANK: name everything that graduates — rule, earmark, todo, pin — as exact paste-ready lines, plus the exact lines to delete.
  4. DANGLING: what carries forward unbanked? One line each, no essays.
  5. SEED: the adhoc.txt lines (and TODO_SLUGS if narrative context is needed) for the next ride’s first compile.
  6. 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.
  7. NOTARIZE: name the sealed artifact of record — the newest hash-stamped cartridge (foo--NN.zip) — and state that its CRC-sealed, byte-reproducible archive is the witnessed receipt of this ride. The seal is the signature; the archive is the deed.

FINALITY: after beat 6, this discussion is CLOSED. Emit NO five-car train, NO probes, NO patches, 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.

Fable 5.1 Medium: Context verified. One live receipt in this compile, and it is the AFTER reading this ride was built to take.

1. VERIFY

Goal: put the article headline at the top of every Google Doc, existing and future, without a throwaway script polluting the repo. Met. This compile’s receipt exports the latest Doc as markdown and its first heading is now # **The Scientific Method of Vibe-Coding: Engineering Replayable AI Workflows**; your hand-run BEFORE of the identical command opened on ## **Setting the Stage…**. Same instrument, one change between, the reading moved where predicted. (The bold inside the heading is Drive’s markdown exporter, not our HTML; it bolds the H2 the same way.) Ignition: the armed --force upsert, and it fired, because the Doc’s content changed. What this compile does not witness is the full sweep: the 🏁 counters were still accruing when you dismounted, so “~1,440 Updated, 0 Failed” is a prediction, not a receipt. n=1 on the after side, honestly labeled. The BrokenPipeError in stderr is head -8 closing the pipe; cosmetic.

2. BANK

Receipt line, top of the RECEIPTS block in foo_files.py, delete the bottom line to hold 20:

# 2026-09-03 googledocizer H1 headline: Doc body now opens with <h1> of the frontmatter title (commit 649f9b0f); straddle closed on the latest Doc's markdown export (## Setting the Stage -> # <title>); catch-up was the EXISTING --force flag, no /tmp script; full-sweep 🏁 counters NOT witnessed at dismount (n=1).

Rule, into the constitution block:

# THE ONE-FENCE-PER-HAND-STEP RULE (banked 2026-09-03). Operators paste fenced blocks whole; a smoke command and the full sweep it was meant to gate, sharing one fence, fire together and the smoke never happens. When a hand-run step exists to be INSPECTED before the next, each step gets its own fence.

Earmark update: the --restamp earmark in googledocizer.py calls --force “the wrong-sized tool.” It still is for a stamp-string repair; this ride showed it is exactly the right-sized tool for a template change. No text change needed; the receipt line above is the annotation.

3. DANGLING

  • The sweep’s 🏁 line lives only in your terminal scrollback; paste it into the next ride’s prompt or it is lost to compaction.
  • If the sweep died mid-corpus, a plain re-run skips the stragglers (still “fresh” by mtime); the remedy is --force from the top.
  • H1 is the bare title, not the dated YYYY-MM-DD (n) | title filename form; swapping is one argument if you change your mind after seeing a few.
  • fetch_file could swallow SIGPIPE so | head stops printing a traceback; one line, low priority.
  • The commit-ledger div for this article needs 649f9b0f.
  • The nap.

4. SEED

# CONTEXT: 3 -- witness the sweep: paste the 🏁 line from the terminal into prompt.md
! .venv/bin/python scripts/articles/googledocizer.py "$(.venv/bin/python -c 'import json,re;p=json.load(open("/home/mike/.config/pipulate/last_published.json"))["1"]["path"];print(re.search(r"/document/d/([\w-]+)",open(p).read()).group(1))')" | head -3
! .venv/bin/python scripts/articles/googledocizer.py -t 1 2>&1 | tail -2
scripts/articles/googledocizer.py

5. CLOSING

This one started with a dread of the catch-up: a thousand-odd documents to redo, a script you’d write once and throw away, the sort of thing that leaves a smear on a repo forever. It ended with a flag that was already there, put there weeks ago for exactly this reason by whoever was sitting here then, with a comment explaining itself. That’s what the receipts are for. The tool remembered what you didn’t have to. The fix itself was five lines, and the proof was one command run twice: the same Doc, exported before and after, opening on a subtitle and then on its title. The only lesson that wasn’t in the diff was the one about your hands: the probe habit taught them to paste stacked commands, and the stack fired the sweep before the smoke test. No harm, and now there’s a rule so the next train knows to separate its cars. Every Doc in the folder is getting its name back while you sleep, one at a time, with a five-hundredth of a second between them.

6. NOTARIZE

The artifact of record is the newest foo-<hash8>-NN.zip in the repo root, rotated by this compile; its hash is not in this payload and I won’t invent it. Its CRC-sealed, byte-reproducible archive holds the raw googledocizer.py as patched, the export receipt with the H1 at the top, and the diff at 649f9b0f. The seal is the signature; the archive is the deed; this discussion is closed.


Book Analysis

Ai Editorial Take

What stands out in this entry is the realization that an existing flag (–force) already solves the bulk-processing requirement. Too often, developers rush to write bespoke migration scripts when a well-designed tool already anticipates systemic updates. It highlights the value of deeply knowing your own codebase before engineering new solutions.

🐦 X.com Promo Tweet

Fixing Google Doc publishing pipelines without throwing away clean architecture. Learn how a simple HTML injection preserves your frontmatter titles across automated exports. https://mikelev.in/futureproof/fixing-google-doc-titles-pipeline-repairs/ #TechWorkflow #Python #Automation

Title Brainstorm

  • Title Option: Fixing Google Doc Titles: Automated Pipeline Repairs in the Age of AI
    • Filename: fixing-google-doc-titles-pipeline-repairs
    • Rationale: Directly addresses the core technical problem and solution while fitting cleanly into the modern book tapestry.
  • Title Option: Repairing the Publishing Pipeline: Injecting Titles into Google Docs
    • Filename: repairing-publishing-pipeline-google-docs
    • Rationale: Emphasizes the systems-engineering perspective of maintaining multi-target publishing frameworks.
  • Title Option: The Anatomy of a Pipeline Patch: Cleaning Up Automated Document Generation
    • Filename: anatomy-of-a-pipeline-patch-document-generation
    • Rationale: Focuses on the methodological approach to executing safe, repeatable script upgrades.

Content Potential And Polish

  • Core Strengths:
    • Clear problem-and-solution narrative structure
    • Practical integration of existing CLI flags (–force) instead of writing throwaway code
    • Transparent verification steps using shell probes
  • Suggestions For Polish:
    • Group the terminal diff blocks slightly tighter to emphasize the minimal nature of the patch
    • Highlight the distinction between one-off scripts and permanent tool extensions

Next Step Prompts

  • Verify the completion counters of the full batch sweep across all remote documents.
  • Explore how markdown metadata converters handle edge cases in nested heading hierarchies.