The Pruning: Reducing Technical Debt in Public Documentation

🤖 Read Raw Markdown

Setting the Stage: Context for the Curious Book Reader

This entry documents the transition from a fragmented, multi-surface documentation strategy to a streamlined ‘launchpad’ model. It captures the reality of maintenance debt and provides a blueprint for using repository-centric truth to simplify public-facing interfaces.


Technical Journal Entry Begins

🔗 Verified Pipulate Commits:

MikeLev.in: Wow, alright. These article-cuts are such logical breaks between work and fresh starting points for new work. All the work you did up to this point can be used to catapult your momentum forward again with either a latest or latestn depending on how broad of an overview you want. It’s either what have we been doing recently or we prompt all the context that will fit. Explain it to them. Explain which choice I made. Explain what you see. And you’re always to fast on a patch and slow on a cheapest falsifying probe, Grok. So if you’re going to end on a strong, competitive note which seems to be how you’re fine-tuned, then favor detritus-free falsifying article-building ignorance dispelling probes.

Grok Fast: Context verified.

The choice made here was latest (via the python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs command baked into the context artifact). This pulls the two most recent full articles (2026-06-28-closing-the-reproducibility-stack.md and 2026-06-28-terminal-telemetry-onboarding.md) plus supporting codebase telemetry instead of the much broader latestn (full historical corpus).

Why this choice? It keeps the context artifact focused, dense, and actionable for the immediate “catapult” — fresh momentum after an article-cut without drowning in 100+ older pieces. The two articles form a tight narrative pair: one on closing the reproducibility stack at the app layer with the magic cookie pattern (GitOps + Nix), the other on polishing the first-10-minutes onboarding via truthful terminal telemetry. This is exactly the seam where your current work sits: reproducible deployment + invisible/frictionless user experience.

What I See (High-Signal Read)

  • Momentum preserved: The recent chisel-strikes (JupyterLab success-path URL printing in flake.nix, anti-nesting nudges, instrumentation) directly feed the “terminal as truthful cockpit” theme. The reproducibility article names the pfSense router as the current frontier — a natural next probe.
  • Kuhn-exemplar thread: You’re not just shipping code; you’re living the candidate exemplar for app-tier GitOps-on-Nix. The magic cookie solves the bootstrap paradox cleanly. Terminal telemetry solves the “first 10 minutes” adoption killer.
  • Honest boundary: Everything below the router snaps back into existence from text. That’s powerful, falsifiable telemetry.
  • Next leverage surface: The onboarding polish is 80% there (success-path links, clear choreography), but public landing copy (Pipulate.com/index.md) and any remaining silent-failure paths still need alignment. The articles themselves are strong but could benefit from tighter cross-links and a “what just happened” recap for readers jumping in mid-stream.

Cheapest falsifying probe (before any patch or new article drafting):

rg -c 'BOTH TABS' /home/mike/repos/Pipulate.com/index.md

(Or rg -c 'JupyterLab' flake.nix to confirm the recent instrumentation.) This quickly shows if documentation drift still exists post-flake edit. Run it, share output if it surprises, and we stay detritus-free.

This setup catapults cleanly: the reproducibility piece gives the “why” and big picture; the telemetry piece gives the “how it feels on day one.” We can now either:

  1. Draft a short follow-up bridging them (e.g., “The Router Probe” or “Magic Cookie Implementation Guide”), or
  2. Sand the remaining onboarding seams with targeted SEARCH/REPLACE blocks.

Where do you want to push — next article draft, specific patch on index.md/flake.nix, or a fresh Mac install probe transcript to falsify the golden path? I’m locked in on the grind that makes the exemplar spreadable.

MikeLev.in: I do the probe:

(nix) pipulate $ rg -c 'BOTH TABS' /home/mike/repos/Pipulate.com/index.md
1
(nix) pipulate $ 

I think we want to get the public messaging right. Opus?

Claude Opus 4.8 Low: Context verified. The three public-messaging surfaces (index.md, install.md) plus their ground truth (install.sh and flake.nix) are all here, which is exactly the set needed — no missing files for this task.

You chose latest (the embedded lsa.py --reverse command + the installer/flake bundle), not latestn. That was the right call: this is a “fresh start on a known seam” turn, not a “reload the whole corpus” turn. Grok read that correctly. Where I’d slow it down is the diagnosis, because your probe already found more than one bug and the patch isn’t the interesting part yet.

Here’s what the code — not my priors — says is actually wrong, ordered by leverage.

1. The “BOTH TABS” promise is false on first run, and it’s the same honesty bug you just fixed in the terminal. Your rg -c confirmed one occurrence in index.md. But look at flake.nix: autoOpenFastHTML = "false", and the open gate is if [ "${autoOpenFastHTML}" = "true" ] || [ -f Notebooks/data/.onboarded ]. On a fresh install .onboarded does not exist, so the FastHTML tab deliberately does not open. First run = one tab (Jupyter). The flake telemetry is now honest about this (“Finish the Onboarding notebook to unlock it automatically next time”), but the public copy still promises two. Worse, index.md contradicts itself: “Success looks like: a JupyterLab Notebook tab opens” (true, singular) sitting in the same file as a “wait for BOTH TABS” line. That’s the public-copy version of exactly the drift your terminal-telemetry-onboarding work just closed at the shell layer. Highest leverage because it’s the same principle, one layer up.

2. install.md’s reset command contradicts the installer’s own first line. The reset says curl -L https://pipulate.com/install.sh | sh -s TestProject. The installer’s first guard is if [ -z "${BASH_VERSION:-}" ] → hard error telling the user to use bash. So on any box where sh is dash (Debian/Ubuntu/WSL — your stated support targets), that documented reset command fails immediately by design. Self-falsifying doc.

3. nix develop is a macOS footgun in index.md. index.md says launch and re-run with bare nix develop. But ./run (which the installer builds) does exec nix develop --impure on Darwin. Bare nix develop without --impure won’t behave the same on a Mac, and inside an active shell your anti-nesting nix() function intercepts it anyway. install.md already uses the correct ./run. The two pages disagree on the one command users type most.

4. Cosmetic-but-real divergence: -fsSL vs -L, ~/pipulate vs ~/TestProject, and the with/without custom-name forms differ across the two pages. Not bugs, but they make the two front doors look like two different products.

The strategic recommendation before any patch: standardize the re-entry verb on ./run everywhere user-facing. It’s the artifact install.sh actually creates, it encapsulates the --impure OS split, and it sidesteps the anti-nesting nudge. That single decision resolves #3 and most of #4 at once, and it’s the thing your own installer already treats as canonical.

The cheapest falsifying probes I’d want before touching anything — in your spirit, dispel the ignorance, don’t assume:

  • rg -n 'BOTH TABS' /home/mike/repos/Pipulate.com/index.md /home/mike/repos/Pipulate.com/install.md — pin the exact lines (your -c gave the count, not the location) so the SEARCH blocks land cleanly.
  • One thing I genuinely cannot verify from this context: that https://pipulate.com/install.sh (root, via the _config.yml include + the “release.py puts it here” comment) serves byte-identical content to assets/installer/install.sh. If release.py rewrites it, my reading of the bash guard could be stale. Probe: curl -sSL https://pipulate.com/install.sh | diff - assets/installer/install.sh. If that’s clean, every claim above is solid.

If those two come back as expected, I’ll write the SEARCH/REPLACE set — I’d sequence it as #2 (the broken reset, smallest and unambiguous) → #1 (the tab honesty fix, highest value) → #3/#4 folded into the ./run standardization. But I’d rather see the rg -n and the diff first than patch on faith. Which probe do you want to run?

MikeLev.in: Here’s the probe:

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

nothing to commit, working tree clean
(nix) pipulate $ rg -n 'BOTH TABS' /home/mike/repos/Pipulate.com/index.md /home/mike/repos/Pipulate.com/install.md
/home/mike/repos/Pipulate.com/install.md
61:Wait for ***BOTH TABS*** to auto-open in your browser.

/home/mike/repos/Pipulate.com/index.md
58:Wait for ***BOTH TABS*** to auto-open in your browser.
(nix) pipulate $ curl -sSL https://pipulate.com/install.sh | diff - assets/installer/install.sh
(nix) pipulate $ 

And by the way, we’re trying to purge the ./run approach in favor of just going with nix develop all the time because there are varying host OS environment fragility issues here. So we will abide by the golden path here and not make up our own obscure rules. We will stick to the most frequent Nix golden path and try to instill some habits.

Here’s some more curtain reveal too. I try to hold back on the distraction, but hate release system will for example provide some illumination. Are we ready for a patch to fix public positioning? Oh, and maybe the README too!

Claude Opus 4.8 Low: Context verified. Both probes came back clean and decisive: the served install.sh is byte-identical to assets/installer/install.sh (empty diff), so the bash-guard reading is solid, and BOTH TABS lives at exactly install.md:61 and index.md:58. That kills the one thing I couldn’t verify last turn.

Two notes that change the plan from where Opus left it:

  1. You’re purging ./run in favor of bare nix develop — so the fix is the opposite of standardizing on ./run. Good news: the installer’s own first-run hand-off already runs bare nix develop -L (no --impure) even on Darwin, so the docs were already lying by telling repeat-users to do something different from what the installer does on launch. Collapsing everything to nix develop makes the docs match the installer’s actual behavior.
  2. The honesty bug is bigger than one line. On a fresh install .onboarded doesn’t exist and autoOpenFastHTML = "false", so only the Jupyter tab opens. Every “BOTH TABS” / “Two browser tabs” promise is false on first run — the same drift you just closed at the shell layer, one level up.

Here’s the patch set. Ordered so the ambiguous cd ~/TestProject\n./run in install.md gets disambiguated by patching the Big Reset block first.

Target: /home/mike/repos/Pipulate.com/install.md
[[[SEARCH]]]
# 2. Launch it
cd ~/TestProject && ./run
[[[DIVIDER]]]
# 2. Launch it
cd ~/TestProject && nix develop
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/install.md
[[[SEARCH]]]
**Success looks like:** Two browser tabs auto-open showing the Pipulate interface and JupyterLab.
[[[DIVIDER]]]
**Success looks like:** The JupyterLab tab auto-opens in your browser. Run the Onboarding notebook top-to-bottom to unlock the Pipulate app (it opens automatically on future launches).
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/install.md
[[[SEARCH]]]
rm -rf ~/TestProject
curl -L https://pipulate.com/install.sh | sh -s TestProject
cd ~/TestProject
./run
[[[DIVIDER]]]
rm -rf ~/TestProject
curl -L https://pipulate.com/install.sh | bash -s TestProject
cd ~/TestProject
nix develop
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/install.md
[[[SEARCH]]]
cd ~/TestProject
./run
[[[DIVIDER]]]
cd ~/TestProject
nix develop
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/install.md
[[[SEARCH]]]
Wait for ***BOTH TABS*** to auto-open in your browser.
[[[DIVIDER]]]
Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/install.md
[[[SEARCH]]]
4. `cd ~/TestProject && ./run`
[[[DIVIDER]]]
4. `cd ~/TestProject && nix develop`
[[[REPLACE]]]

Now index.md — its quick-start already uses nix develop, so it only needs the honesty fix:

Target: /home/mike/repos/Pipulate.com/index.md
[[[SEARCH]]]
Wait for ***BOTH TABS*** to auto-open in your browser.
[[[DIVIDER]]]
Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
[[[REPLACE]]]

And the README — token-scoped replacements to dodge the fence-stripping hazard and the alignment-whitespace trap:

Target: /home/mike/repos/pipulate/README.md
[[[SEARCH]]]
curl -L https://pipulate.com/install.sh | bash -s TestProject
cd ~/TestProject
./run
[[[DIVIDER]]]
curl -L https://pipulate.com/install.sh | bash -s TestProject
cd ~/TestProject
nix develop
[[[REPLACE]]]
Target: /home/mike/repos/pipulate/README.md
[[[SEARCH]]]
Wait for ***BOTH TABS*** to auto-open in your browser.
[[[DIVIDER]]]
Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
[[[REPLACE]]]
Target: /home/mike/repos/pipulate/README.md
[[[SEARCH]]]
cd ~/pipulate && ./run
[[[DIVIDER]]]
cd ~/pipulate && nix develop
[[[REPLACE]]]
Target: /home/mike/repos/pipulate/README.md
[[[SEARCH]]]
git pull && ./run
[[[DIVIDER]]]
git pull && nix develop
[[[REPLACE]]]
Target: /home/mike/repos/pipulate/README.md
[[[SEARCH]]]
**Nix Environment Activation:** Always run `./run` (a.k.a. `nix develop`) from the `~/pipulate` directory
[[[DIVIDER]]]
**Nix Environment Activation:** Always run `nix develop` from the `~/pipulate` directory
[[[REPLACE]]]

The one thing I still can’t prove from this context, in your falsifying spirit: the installer creates a ./run that adds --impure on Darwin, and bare first-run skips it — so there’s an unverified question of whether a repeat bare nix develop succeeds on macOS without --impure. The first-run evidence suggests it does, and you’ve decided bare nix develop is the golden-path habit, so I followed that and didn’t invent an --impure rule. If you want to dispel it rather than assume: on a Mac, cd ~/pipulate && nix develop and watch for an impurity/builtins.currentSystem error. If it’s clean, the docs are now fully truthful; if not, that’s a flake-level fix, not a docs fix.

I left the cosmetic curl -fsSL vs -L and ~/pipulate vs ~/TestProject divergence alone — that’s a “make the two front doors look like one product” pass, separate from this honesty/golden-path one. Say the word and I’ll standardize those next.

MikeLev.in: Okay, the first round of patches on Pipulate.com:

(nix) Pipulate.com $ git --no-pager diff
diff --git a/install.md b/install.md
index 07a11c0..e629a37 100644
--- a/install.md
+++ b/install.md
@@ -22,12 +22,12 @@ curl -L https://pipulate.com/install.sh | bash -s TestProject
 
 [triple backtick]bash
 # 2. Launch it
-cd ~/TestProject && ./run
+cd ~/TestProject && nix develop
[triple backtick]
 
 **What you get:** A local web app at `http://localhost:5001` with step-by-step workflows, integrated AI chat, and a JupyterLab instance at `http://localhost:8888`. No cloud required.
 
-**Success looks like:** Two browser tabs auto-open showing the Pipulate interface and JupyterLab.
+**Success looks like:** The JupyterLab tab auto-opens in your browser. Run the Onboarding notebook top-to-bottom to unlock the Pipulate app (it opens automatically on future launches).
 
 These few commands:
 - ✅ Updates to the latest version automatically
@@ -44,7 +44,7 @@ These few commands:
 
 [triple backtick]bash
 cd ~/TestProject
-./run
+nix develop
 [triple backtick]
 
 ### 5. The Big Reset (If Necessary)
@@ -53,12 +53,12 @@ Things sometimes go wrong. This is how you do a full Pipulate reset. This will a
 
 [triple backtick]bash
 rm -rf ~/TestProject
-curl -L https://pipulate.com/install.sh | sh -s TestProject
+curl -L https://pipulate.com/install.sh | bash -s TestProject
 cd ~/TestProject
-./run
+nix develop
 [triple backtick]
 
-Wait for ***BOTH TABS*** to auto-open in your browser.
+Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
 
 
 ### Installation Strategy: Universal First, PyPI Alternative
@@ -131,7 +131,7 @@ If you need to force-close everything:
 1. Press `Ctrl+C` repeatedly until the terminal exits
 2. Close the terminal window
 3. Open a new terminal
-4. `cd ~/TestProject && ./run`
+4. `cd ~/TestProject && nix develop`
 
 ### Uninstalling Nix
 
(nix) Pipulate.com $ git commit -am "Honesty changes"
[main bb82fb8] Honesty changes
 1 file changed, 7 insertions(+), 7 deletions(-)
(nix) Pipulate.com $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 525 bytes | 525.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:miklevin/Pipulate.com
   1fd3f09..bb82fb8  main -> main
(nix) Pipulate.com $

Oh, and this one:

(nix) Pipulate.com $ git --no-pager diff
diff --git a/index.md b/index.md
index 6b671cd..8defdbb 100644
--- a/index.md
+++ b/index.md
@@ -55,7 +55,7 @@ rm -rf ~/pipulate
 curl -fsSL https://pipulate.com/install.sh | bash
 [triple backtick]
 
-Wait for ***BOTH TABS*** to auto-open in your browser.
+Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
 
 ---
 
(nix) Pipulate.com $ git commit -am "And the last touch"
[main 325a2fe] And the last touch
 1 file changed, 1 insertion(+), 1 deletion(-)
(nix) Pipulate.com $ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 380 bytes | 380.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:miklevin/Pipulate.com
   bb82fb8..325a2fe  main -> main
(nix) Pipulate.com $

Okay, now onto the main Pipulate repo.

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

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/pipulate/README.md'.
(nix) pipulate $ d
diff --git a/README.md b/README.md
index 3748243c..eb17ed67 100644
--- a/README.md
+++ b/README.md
@@ -514,7 +514,7 @@ The installation procedures labeled "Quick Start Guide" and the main installatio
 rm -rf ~/TestProject
 curl -L https://pipulate.com/install.sh | bash -s TestProject
 cd ~/TestProject
-./run
+nix develop
 [triple backtick]
 
 Wait for ***BOTH TABS*** to auto-open in your browser.
(nix) pipulate $ m
📝 Committing: chore: Update installation script to use nix develop
[main 0508f92e] chore: Update installation script to use nix develop
 1 file changed, 1 insertion(+), 1 deletion(-)
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/pipulate/README.md'.
(nix) pipulate $ d
diff --git a/README.md b/README.md
index eb17ed67..ec5b936e 100644
--- a/README.md
+++ b/README.md
@@ -517,7 +517,7 @@ cd ~/TestProject
 nix develop
 [triple backtick]
 
-Wait for ***BOTH TABS*** to auto-open in your browser.
+Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
 
 ### 🚨 Installation Troubleshooting
 
(nix) pipulate $ m
📝 Committing: fix: Update onboarding instructions
[main 53a8862f] fix: Update onboarding instructions
 1 file changed, 1 insertion(+), 1 deletion(-)
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/pipulate/README.md'.
(nix) pipulate $ d
diff --git a/README.md b/README.md
index ec5b936e..0e55e127 100644
--- a/README.md
+++ b/README.md
@@ -1355,7 +1355,7 @@ create_workflow.py               │ └──────┬──────
 ### Essential Commands
 [triple backtick]bash
 # Development workflow
-cd ~/pipulate && ./run          # Start Pipulate
+cd ~/pipulate && nix develop          # Start Pipulate
 nix develop .#quiet                   # Start without auto-services
 python server.py                     # Manual server start
 git pull && ./run              # Update to latest
(nix) pipulate $ m
📝 Committing: chore: Update startup command in README
[main a67670ec] chore: Update startup command in README
 1 file changed, 1 insertion(+), 1 deletion(-)
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/pipulate/README.md'.
(nix) pipulate $ d
diff --git a/README.md b/README.md
index 0e55e127..d6879a58 100644
--- a/README.md
+++ b/README.md
@@ -1358,7 +1358,7 @@ create_workflow.py               │ └──────┬──────
 cd ~/pipulate && nix develop          # Start Pipulate
 nix develop .#quiet                   # Start without auto-services
 python server.py                     # Manual server start
-git pull && ./run              # Update to latest
+git pull && nix develop              # Update to latest
 
 # Create new workflows  
 python helpers/workflow/create_workflow.py my_workflow.py MyClass my_internal_name
(nix) pipulate $ m
📝 Committing: fix: update workflow start command
[main f0c1d107] fix: update workflow start command
 1 file changed, 1 insertion(+), 1 deletion(-)
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/pipulate/README.md'.
(nix) pipulate $ d
diff --git a/README.md b/README.md
index d6879a58..afb37f04 100644
--- a/README.md
+++ b/README.md
@@ -768,7 +768,7 @@ Traditional development follows DRY principles, creating abstract, complex syste
 
 ## Developer Setup & Environment Notes
 
-**Nix Environment Activation:** Always run `./run` (a.k.a. `nix develop`) from the `~/pipulate` directory *before* running any project commands (`python server.py`, `pip install`, etc.) in a new terminal. This ensures you are using the correct dependencies defined in `flake.nix`.
+**Nix Environment Activation:** Always run `nix develop` from the `~/pipulate` directory *before* running any project commands (`python server.py`, `pip install`, etc.) in a new terminal. This ensures you are using the correct dependencies defined in `flake.nix`.
 
 **Interactive vs. Quiet Shell:**
 
(nix) pipulate $ m
📝 Committing: fix: Ensure Nix environment activation in README
[main 74fb8212] fix: Ensure Nix environment activation in README
 1 file changed, 1 insertion(+), 1 deletion(-)
(nix) pipulate $ git push
Enumerating objects: 17, done.
Counting objects: 100% (17/17), done.
Delta compression using up to 48 threads
Compressing objects: 100% (15/15), done.
Writing objects: 100% (15/15), 1.56 KiB | 1.56 MiB/s, done.
Total 15 (delta 10), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (10/10), completed with 2 local objects.
To github.com:pipulate/pipulate.git
   32fb0164..74fb8212  main -> main
(nix) pipulate $

There! How does that look? Everything applied? Everything land correctly?

ChatGPT Extended Pro: Context verified.

The choice made here was latest (via the python scripts/articles/lsa.py -t 1 --reverse --fmt dated-slugs command baked into the context artifact). This pulls the two most recent full articles (2026-06-28-closing-the-reproducibility-stack.md and 2026-06-28-terminal-telemetry-onboarding.md) plus supporting codebase telemetry instead of the much broader latestn (full historical corpus).

Why this choice? It keeps the context artifact focused, dense, and actionable for the immediate “catapult” — fresh momentum after an article-cut without drowning in 100+ older pieces. The two articles form a tight narrative pair: one on closing the reproducibility stack at the app layer with the magic cookie pattern (GitOps + Nix), the other on polishing the first-10-minutes onboarding via truthful terminal telemetry. This is exactly the seam where your current work sits: reproducible deployment + invisible/frictionless user experience.

What I See (High-Signal Read)

  • Momentum preserved: The recent chisel-strikes (JupyterLab success-path URL printing in flake.nix, anti-nesting nudges, instrumentation) directly feed the “terminal as truthful cockpit” theme. The reproducibility article names the pfSense router as the current frontier — a natural next probe.
  • Kuhn-exemplar thread: You’re not just shipping code; you’re living the candidate exemplar for app-tier GitOps-on-Nix. The magic cookie solves the bootstrap paradox cleanly. Terminal telemetry solves the “first 10 minutes” adoption killer.
  • Honest boundary: Everything below the router snaps back into existence from text. That’s powerful, falsifiable telemetry.
  • Next leverage surface: The onboarding polish is 80% there (success-path links, clear choreography), but public landing copy (Pipulate.com/index.md) and any remaining silent-failure paths still need alignment. The articles themselves are strong but could benefit from tighter cross-links and a “what just happened” recap for readers jumping in mid-stream.

Cheapest falsifying probe (before any patch or new article drafting):

rg -c 'BOTH TABS' /home/mike/repos/Pipulate.com/index.md

(Or rg -c 'JupyterLab' flake.nix to confirm the recent instrumentation.) This quickly shows if documentation drift still exists post-flake edit. Run it, share output if it surprises, and we stay detritus-free.

This setup catapults cleanly: the reproducibility piece gives the “why” and big picture; the telemetry piece gives the “how it feels on day one.” We can now either:

  1. Draft a short follow-up bridging them (e.g., “The Router Probe” or “Magic Cookie Implementation Guide”), or
  2. Sand the remaining onboarding seams with targeted SEARCH/REPLACE blocks.

Where do you want to push — next article draft, specific patch on index.md/flake.nix, or a fresh Mac install probe transcript to falsify the golden path? I’m locked in on the grind that makes the exemplar spreadable.

MikeLev.in: Here’s the probe:

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

nothing to commit, working tree clean
(nix) pipulate $ rg -n 'BOTH TABS|Two browser tabs|Opens web interfaces|\.\/run' \
  README.md /home/mike/repos/Pipulate.com/index.md /home/mike/repos/Pipulate.com/install.md
/home/mike/repos/Pipulate.com/index.md
34:- ✅ Opens web interfaces in your browser

/home/mike/repos/Pipulate.com/install.md
35:- ✅ Opens web interfaces in your browser

README.md
39:- ✅ Opens web interfaces in your browser
150:    │  │  Web Browser    │    │        │ │ nix develop (./run) │ │    |\\/| o
577:- ✅ Opens web interfaces in your browser
589:│ 1. Download install.sh       │         │ 5. User runs './run' ('nix develop')       │
670:1. **Two browser tabs open automatically:**
775:**Standard Shell:** `./run` (same as `nix develop` or `nix develop .#default`) runs the startup script (`run-script` defined in `flake.nix`) with welcome messages and service startup. Ideal for general use.
(nix) pipulate $ 

Give me my small semantic cleanup pass, please and thank you.

Claude Opus 4.8 Low: Context verified. The probe cleared BOTH TABS everywhere and shows what’s left. Two of those hits are honest-to-goodness cleanup; the rest I’d argue you leave alone, and I’ll say why.

The two worth patching:

Line 670 is the same lie one more layer down — “Two browser tabs open automatically” is false on first run, where only Jupyter appears.

Target: README.md
[[[SEARCH]]]
1. **Two browser tabs open automatically:**
   - `localhost:5001` - Pipulate web interface with navigation menu
   - `localhost:8888` - JupyterLab for development/experimentation
[[[DIVIDER]]]
1. **JupyterLab opens automatically** for onboarding. Completing the Onboarding notebook unlocks the Pipulate app, which then opens automatically on future launches:
   - `localhost:8888` - JupyterLab for development/experimentation (opens first)
   - `localhost:5001` - Pipulate web interface with navigation menu
[[[REPLACE]]]

Line 775 is the last prose endorsement of ./run as the canonical verb.

Target: README.md
[[[SEARCH]]]
**Standard Shell:** `./run` (same as `nix develop` or `nix develop .#default`) runs the startup script (`run-script` defined in `flake.nix`) with welcome messages and service startup. Ideal for general use.
[[[DIVIDER]]]
**Standard Shell:** `nix develop` (the same as `nix develop .#default`) runs the startup script (`run-script` defined in `flake.nix`) with welcome messages and service startup. Ideal for general use.
[[[REPLACE]]]

What I’m deliberately not patching, and the falsifying reason for each:

Lines 150 and 589 are ./run references inside ASCII-art boxes (│ nix develop (./run) │ and 5. User runs './run' ('nix develop')). A SEARCH/REPLACE there shrinks the cell content and breaks the border alignment — the exact spacing trap your whole tokenizer-grain discipline warns against. Removing ./run from these is a box-redraw job, not a string swap. Leave them until you’re redrawing those diagrams anyway; they currently show nix develop as the real command with ./run only as a parenthetical, so they aren’t actively lying.

The three - ✅ Opens web interfaces in your browser lines (39, 577, and install.md/index.md 34–35) are vague rather than false: on a settled install both interfaces do open. They’re not the “BOTH TABS” overpromise. I’d leave them unless you want to tighten the wording to “Opens JupyterLab in your browser” for first-run precision — but that’s a wording-polish pass, not an honesty fix.

So: two blocks above land the semantic cleanup; the ASCII ./run removals are a separate diagram pass best done deliberately.

MikeLev.in: Okay, I do the 2 patches:

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

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'README.md'.
(nix) pipulate $ d
diff --git a/README.md b/README.md
index afb37f04..f6544d05 100644
--- a/README.md
+++ b/README.md
@@ -667,9 +667,9 @@ Just as a chef talks about knife techniques while a diner just wants amazing pas
 **After installation succeeds, here's what to expect:**
 
 ### What You'll See
-1. **Two browser tabs open automatically:**
+1. **JupyterLab opens automatically** for onboarding. Completing the Onboarding notebook unlocks the Pipulate app, which then opens automatically on future launches:
+   - `localhost:8888` - JupyterLab for development/experimentation (opens first)
    - `localhost:5001` - Pipulate web interface with navigation menu
-   - `localhost:8888` - JupyterLab for development/experimentation
 
 2. **In the Pipulate interface:**
    - Left sidebar with workflow plugins (Introduction, Profiles, etc.)
(nix) pipulate $ m
📝 Committing: docs: Update onboarding flow description
[main 8ec68d36] docs: Update onboarding flow description
 1 file changed, 2 insertions(+), 2 deletions(-)
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated 'README.md'.
(nix) pipulate $ d
diff --git a/README.md b/README.md
index f6544d05..dab34ae3 100644
--- a/README.md
+++ b/README.md
@@ -772,7 +772,7 @@ Traditional development follows DRY principles, creating abstract, complex syste
 
 **Interactive vs. Quiet Shell:**
 
-**Standard Shell:** `./run` (same as `nix develop` or `nix develop .#default`) runs the startup script (`run-script` defined in `flake.nix`) with welcome messages and service startup. Ideal for general use.
+**Standard Shell:** `nix develop` (the same as `nix develop .#default`) runs the startup script (`run-script` defined in `flake.nix`) with welcome messages and service startup. Ideal for general use.
 
 **Quiet Shell:** `nix develop .#quiet` activates the Nix environment *without* running the full startup script or launching services automatically. It only sets up paths and installs pip requirements. Use this for:
 - Running specific commands without starting the servers (e.g., `nix develop .#quiet --command python -c "import pandas"`).
(nix) pipulate $ m
📝 Committing: fix: Update standard shell command in README
[main c3b6dddb] fix: Update standard shell command in README
 1 file changed, 1 insertion(+), 1 deletion(-)
(nix) pipulate $ git push
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 48 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 767 bytes | 767.00 KiB/s, done.
Total 6 (delta 4), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (4/4), completed with 2 local objects.
To github.com:pipulate/pipulate.git
   74fb8212..c3b6dddb  main -> main
(nix) pipulate $

And that’s just bringing the happy-path documentation into alignment with reality. But we have a much bigger site-trimming ahead of us. I don’t like the documentation surface-area on the public site. It’s just more stuff to get out of sync. Currently, I have a bunch of about-like pages on Pipulate.com and then this entire blog-like thing.

Given the state of the models, I can probably dump just about everything into the context window including all the Jekyll template stuff and we can just do one big Opus patch extravaganza to snip everything out of the site that’s not absolutely critical, getting it down to the bare bone. I don’t want to have to keep Pipulate.com in sync with the README.

As I get the ASCII art in shape, I’ll trickle that out to the README and also to a few key pages on Pipulate.com and that will be all. The repo will do the rest. So anyway, let’s do that eza tree thing…

(nix) Pipulate.com $ eza --tree
.
├── 404.md
├── _config.yml
├── _drafts
│   ├── 2025-04-11-watching-ai-create-its-first-pipulate-plugin.md
│   └── 2025-04-16-pipulate-workflow-ai-prompt.md
├── _guide
│   ├── 2025-01-01-the-wet-blueprint.md
│   ├── 2025-04-06-local-first-revolution.md
│   ├── 2025-04-07-chain-reaction-pattern.md
│   ├── 2025-04-08-the-future-is-simple.md
│   ├── 2025-04-09-beyond-colab.md
│   ├── 2025-04-13-understanding-keys.md
│   ├── 2025-04-14-workflow-abstraction.md
│   ├── 2025-04-15-anatomy-of-a-workflow.md
│   ├── 2025-04-16-kickstarting-your-workflow.md
│   ├── 2025-04-17-expanding-your-workflow.md
│   ├── 2025-04-18-polishing-workflows.md
│   └── template.md
├── _includes
│   ├── articles.html
│   ├── mermaid.html
│   ├── mlgl.html
│   ├── posts-main.html
│   ├── subnav.html
│   └── youtubePlayer.html
├── _layouts
│   ├── default.html
│   ├── forward.html
│   └── post.html
├── _site
│   ├── 404.html
│   ├── about
│   │   └── index.html
│   ├── ai-seo-software.svg
│   ├── AI_CONTEXT
│   │   └── index.html
│   ├── AI_CONTEXT.md
│   ├── assets
│   │   ├── css
│   │   │   ├── normalize.css
│   │   │   ├── pico.css
│   │   │   ├── style.css
│   │   │   ├── styles.css
│   │   │   └── syntax.css
│   │   ├── images
│   │   │   ├── GitHub_Logo_White.png
│   │   │   └── logo-large.516e776d.svg
│   │   └── js
│   │       ├── copy-code.js
│   │       └── site.js
│   ├── AUDIT
│   │   └── index.html
│   ├── AUDIT.md
│   ├── crawler
│   │   └── index.html
│   ├── development
│   │   ├── index.backup
│   │   └── index.html
│   ├── favicon.ico
│   ├── flake.lock
│   ├── flake.nix
│   ├── foo
│   │   └── index.html
│   ├── foss
│   │   └── index.html
│   ├── guide
│   │   ├── anatomy-of-a-workflow
│   │   │   └── index.html
│   │   ├── beyond-colab
│   │   │   └── index.html
│   │   ├── chain-reaction-pattern
│   │   │   └── index.html
│   │   ├── expanding-your-workflow
│   │   │   └── index.html
│   │   ├── index.html
│   │   ├── kickstarting-your-workflow
│   │   │   └── index.html
│   │   ├── local-first-revolution
│   │   │   └── index.html
│   │   ├── polishing-workflows
│   │   │   └── index.html
│   │   ├── the-future-is-simple
│   │   │   └── index.html
│   │   ├── understanding-keys
│   │   │   └── index.html
│   │   ├── wet-blueprint
│   │   │   └── index.html
│   │   └── workflow-abstraction
│   │       └── index.html
│   ├── index.html
│   ├── install
│   │   └── index.html
│   ├── install.sh
│   ├── key.rot
│   ├── keywords
│   │   └── index.html
│   ├── MLGL.html
│   ├── nix-mac
│   │   └── index.html
│   ├── nix-win
│   │   └── index.html
│   ├── pipulate.svg
│   ├── python
│   │   └── index.html
│   ├── README
│   │   └── index.html
│   ├── README.md
│   ├── seo
│   │   └── index.html
│   ├── software
│   │   └── index.html
│   └── testimonials
│       └── index.html
├── about.md
├── ai-seo-software.svg
├── AI_CONTEXT.md
├── assets
│   ├── css
│   │   ├── normalize.css
│   │   ├── pico.css
│   │   ├── styles.css
│   │   └── syntax.css
│   ├── images
│   │   ├── GitHub_Logo_White.png
│   │   └── logo-large.516e776d.svg
│   └── js
│       ├── copy-code.js
│       └── site.js
├── AUDIT.md
├── CNAME
├── crawler.md
├── development.md
├── development.md.backup
├── documentation.md
├── favicon.ico
├── flake.lock
├── flake.nix
├── foo.md
├── foss.md
├── Gemfile
├── Gemfile.backup
├── Gemfile.lock
├── Gemfile.lock.backup
├── guide.md
├── index.md
├── install.md
├── install.sh
├── key.rot
├── keywords.md
├── MLGL.md
├── nix-mac.md
├── nix-win.md
├── pipulate.svg
├── python.md
├── README.md
├── seo.md
├── software.md
└── testimonials.md
(nix) Pipulate.com $ 

That’s not too bad. But almost all of that’s going to have to go. I won’t drop it all in the context window straight away because it is quite a bit. But we’ll trim bit by bit based on what’s actually linked-to by the Jekyll stuff or put there by the release.py script. Or if it’s Nix stuff for building the environment of course that stays. If it’s not one of those things in the critical path that has to stay, then it goes. This is going to be a big purge.

We can really just go in there with a chainsaw and get this down to minimum. Each thing we’re getting ready to delete, I’ll put in context. I’m starting with the obvious candidates on this turn linked to from the menu. If there’s any good ASCII art in there, we can transpose it over to the ascii_displays.py file. Don’t write patches for those transfers though. Just bring the good ASCII art to my attention and I’ll do it with vim. But only if it’s not already in that file or in the main Pipulate repo README.md.

Are you with me?

Grok 2: Context verified. The latest bundle (2 recent articles + core telemetry) keeps momentum tight on the onboarding/reproducibility seam. The public site trim is high-leverage: reduce doc drift by making Pipulate.com a thin landing page that defers to the repo README.md (and a few canonical pages). The eza --tree confirms a lot of historical cruft (_guide/*, old .md files, backups, etc.) that no longer needs to be maintained in Jekyll.

Cheapest Falsifying Probes (run these first)

# 1. What Jekyll actually builds/serves today
cd /home/mike/repos/Pipulate.com && bundle exec jekyll build --trace && echo "Build succeeded; check _site/ size" && du -sh _site/

# 2. Current links/references from core files (pre-trim)
rg -l '\.(md|html)' _config.yml index.md install.md about.md _includes/ _layouts/ | head -10

# 3. Release.py / install.sh dependencies (what must stay)
rg -n 'install\.sh|index\.md|about\.md|flake\.nix' /home/mike/repos/pipulate/release.py assets/installer/install.sh

If those are clean, we chainsaw safely. Goal: minimal viable Jekyll site (~index + install + core assets/layouts + CNAME). Everything else can live in repo README.md, articles (via lsa.py), or be deleted. No more _guide/, scattered .md pages, or backup cruft.

Proposed Trim Strategy

  • Keep: index.md, install.md, about.md (if it has unique value; otherwise fold), _config.yml, CNAME, core _layouts/, _includes/, assets/, install.sh, flake.nix/flake.lock (for reproducibility transparency), favicon.ico, key SVGs.
  • Purge: _guide/, most other top-level .md (foss.md, seo.md, etc.), _drafts/, backups, development.md (defer to repo), documentation.md, etc.
  • ASCII art: I’ll flag good ones for manual vim transfer to imports/ascii_displays.py or README.md. No patches for those yet.
  • Post-trim: Update index.md/install.md to point heavily to GitHub README. Rebuild & verify.

Since this is a big purge, I’ll start with high-confidence deletes/rewrites via full-file [[[WRITE_FILE]]] where appropriate (safe for Jekyll markdown), and surgical SEARCH/REPLACE for precision. We do this iteratively so probes stay cheap.

Next turn after your probes: I’ll deliver the full patch set for the kept files + rm commands for purge (or git rm via patch if preferred). For now, here’s the first surgical pass on the two highest-traffic files to align messaging immediately.

Target: /home/mike/repos/Pipulate.com/index.md
[[[SEARCH]]]
Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
[[[DIVIDER]]]
Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook top-to-bottom to unlock the Pipulate app (it opens automatically on future launches). Full details and troubleshooting in the [repository README](https://github.com/pipulate/pipulate/blob/main/README.md).
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/install.md
[[[SEARCH]]]
Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
[[[DIVIDER]]]
Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook top-to-bottom to unlock the Pipulate app (it opens automatically on future launches). Full reproducible setup and developer notes live in the [repository README](https://github.com/pipulate/pipulate/blob/main/README.md).
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/index.md
[[[SEARCH]]]
**Success looks like:** The JupyterLab tab auto-opens in your browser. Run the Onboarding notebook top-to-bottom to unlock the Pipulate app (it opens automatically on future launches).
[[[DIVIDER]]]
**Success looks like:** The JupyterLab tab auto-opens. Complete the Onboarding notebook to unlock the full Pipulate interface. Everything is reproducible via Nix — see the repo for the full story.
[[[REPLACE]]]

Run the probes, confirm, then say “go” for the full purge pass (I’ll include rm -rf guidance for the cruft dirs after you approve the kept core). This keeps the site honest, tiny, and in sync with reality. Ready?

MikeLev.in: The probes:

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

nothing to commit, working tree clean
(nix) pipulate $ # 1. What Jekyll actually builds/serves today
cd /home/mike/repos/Pipulate.com && bundle exec jekyll build --trace && echo "Build succeeded; check _site/ size" && du -sh _site/

# 2. Current links/references from core files (pre-trim)
rg -l '\.(md|html)' _config.yml index.md install.md about.md _includes/ _layouts/ | head -10

# 3. Release.py / install.sh dependencies (what must stay)
rg -n 'install\.sh|index\.md|about\.md|flake\.nix' /home/mike/repos/pipulate/release.py assets/installer/install.sh
The program 'bundle' is not in your PATH. It is provided by several packages.
You can make it available in an ephemeral shell by typing one of the following:
  nix-shell -p bundler
  nix-shell -p gotools
  nix-shell -p graalvmPackages.truffleruby
  nix-shell -p jruby
  nix-shell -p mastodon
  nix-shell -p ruby
  nix-shell -p ruby_3_1
  nix-shell -p ruby_3_2
  nix-shell -p ruby_3_4
  nix-shell -p zammad
_config.yml
_layouts/default.html
_includes/subnav.html
rg: assets/installer/install.sh: No such file or directory (os error 2)
/home/mike/repos/pipulate/release.py
153:        targets = [PIPULATE_ROOT / "README.md", PIPULATE_COM_ROOT / "index.md"]
394:    """Copies install.sh to Pipulate.com and commits if changed."""
395:    print("\n🔄 Step 3: Synchronizing install.sh to Pipulate.com...")
396:    source_path = PIPULATE_ROOT / "assets/installer/install.sh"
397:    dest_path = PIPULATE_COM_ROOT / "install.sh"
400:        print(f"⚠️  Warning: Pipulate.com repo not found at {PIPULATE_COM_ROOT}. Skipping install.sh sync.")
404:        print(f"⚠️  Warning: Source install.sh not found at {source_path}. Skipping install.sh sync.")
417:            commit_msg = f"chore: Update install.sh from pipulate repo v{get_current_version()}"
434:                    print(f"✅ Pushed install.sh update and set upstream: origin/{current_branch}")
438:                    print("✅ Pushed install.sh update to Pipulate.com repo.")
448:                    print(f"✅ Fallback successful: Pushed install.sh update and set upstream: origin/{current_branch}")
456:            print("✅ install.sh is already up-to-date in Pipulate.com repo.")
1084:    parser.add_argument("--skip-install-sh-sync", action="store_true", help="Skip install.sh synchronization")
1134:        print("\n⏭️  Skipping install.sh synchronization (--skip-install-sh-sync)")
(nix) Pipulate.com $

MikeLev.in: I think Grok just repeated patches that were already done. True? Where are we really at, ChatGPT?

ChatGPT 5.5 Thinking Extended: According to a document from 2026-06-28, Context verified. Yes: Grok mostly repeated patches that were already done. The actual happy-path documentation alignment has landed; Grok’s later “first surgical pass” was stale relative to your commits. At most, it suggested second-order polish like adding README links, not the core honesty fixes.

Where you really are:

1. Pipulate.com honesty fixes are done and pushed. You committed bb82fb8 for the install-page honesty changes, including replacing ./run with nix develop, then committed 325a2fe for the remaining index.md “BOTH TABS” line, replacing it with the JupyterLab-first onboarding truth.

2. Main pipulate README happy-path fixes are done and pushed. The README now says JupyterLab opens first for onboarding, with the Pipulate app unlocked after completing the notebook, and the Standard Shell wording now names nix develop rather than ./run. Those changes were committed as 8ec68d36 and c3b6dddb, then pushed.

3. The remaining work is not another honesty patch; it is the site-surface purge. You are now at the pre-purge audit stage for Pipulate.com. The question is no longer “fix BOTH TABS / ./run drift”; it is “what files are truly in the Jekyll/release critical path, and what can be removed without breaking the site or release flow?”

4. The last probes were useful, but not fully conclusive. The Jekyll build probe did not actually run because bundle was not available in the shell you were in. That does not mean the site is unbuildable; the Pipulate.com flake.nix is explicitly designed to provide Ruby, Jekyll, Bundler support, and a quiet shell for manual checks.

5. One probe was run from the wrong repo perspective. rg ... assets/installer/install.sh failed because you were in /home/mike/repos/Pipulate.com, where that path does not exist. The source installer lives in /home/mike/repos/pipulate/assets/installer/install.sh; release.py copies it into /home/mike/repos/Pipulate.com/install.sh.

6. Current critical path is still bigger than “index + install.” _layouts/default.html still links Home, About, Install, Guide, and Development, so about.md, guide.md, and development.md are currently public nav targets until the layout is changed. _config.yml also still includes install.sh, key.rot, README.md, and AUDIT.md, and it defines the guide collection with /guide/:slug/ output, so guide-related files/config must be removed deliberately, not just deleted. release.py also syncs install.sh, AUDIT.md, and AI_CONTEXT.md to Pipulate.com, so those are release-managed artifacts, not random site cruft.

So the real status is:

DONE:
- Pipulate.com first-run tab honesty fixed.
- Pipulate.com ./run -> nix develop public-path cleanup done.
- README first-run tab honesty fixed.
- README Standard Shell now points to nix develop.
- All of that was committed and pushed.

NOT DONE:
- Pipulate.com surface-area purge.
- Jekyll nav/config simplification.
- Guide/development/about removal or folding.
- Release-managed artifact policy decision.
- Correct build verification under Pipulate.com’s Nix Jekyll shell.

The next clean move is not to apply Grok’s patch. It is to run a corrected audit from the right environment:

cd /home/mike/repos/Pipulate.com
nix develop .#quiet --command bash -lc 'bundle exec jekyll build --trace && du -sh _site'

rg -n 'href="/|site\.guide|collections:|include:|README.md|AUDIT.md|AI_CONTEXT.md|key\.rot|install\.sh' \
  _config.yml index.md install.md about.md guide.md development.md _layouts/ _includes/

rg -n 'PIPULATE_COM_ROOT|README.md|index.md|install.sh|AUDIT.md|AI_CONTEXT.md|key.rot|sync_' \
  /home/mike/repos/pipulate/release.py

My current keep/delete read:

Hard keep for now:
CNAME
_config.yml
Gemfile
Gemfile.lock
flake.nix
index.md
install.md
install.sh
key.rot
README.md
AUDIT.md
AI_CONTEXT.md
_layouts/default.html
assets/css/*
assets/js/*
favicon.ico
logo/svg/image assets referenced by layout or pages

Conditional, do not delete until nav/config are changed:
about.md
guide.md
development.md
documentation.md
_guide/ collection files, if present
_layouts/post.html
_includes/posts-main.html
_includes/subnav.html

Likely early purge candidates after rg/build confirmation:
development.md.backup
Gemfile.backup
Gemfile.lock.backup
crawler.md
foo.md
foss.md
keywords.md
MLGL.md
nix-mac.md
nix-win.md
python.md
seo.md
software.md
testimonials.md
old generated/static blog-like directories

So yes: Grok was directionally aligned, but temporally behind. You are past the happy-path wording fix and standing at the mouth of the actual chainsaw pass.

MikeLev.in: Well, there’s a probe for you:

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

nothing to commit, working tree clean
(nix) pipulate $ cd /home/mike/repos/Pipulate.com
nix develop .#quiet --command bash -lc 'bundle exec jekyll build --trace && du -sh _site'

rg -n 'href="/|site\.guide|collections:|include:|README.md|AUDIT.md|AI_CONTEXT.md|key\.rot|install\.sh' \
  _config.yml index.md install.md about.md guide.md development.md _layouts/ _includes/

rg -n 'PIPULATE_COM_ROOT|README.md|index.md|install.sh|AUDIT.md|AI_CONTEXT.md|key.rot|sync_' \
  /home/mike/repos/pipulate/release.py
warning: updating lock file '/home/mike/repos/Pipulate.com/flake.lock':
• Added input 'flake-utils':
    'github:numtide/flake-utils/11707dc2f618dd54ca8739b309ec4fc024de578b?narHash=sha256-l0KFg5HjrsfsO/JpG%2Br7fRrqm12kzFHyUHqHCVpMMbI%3D' (2024-11-13)
• Added input 'flake-utils/systems':
    'github:nix-systems/default/da67096a3b9bf56a91d16901293e51ba5b49a27e?narHash=sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768%3D' (2023-04-09)
• Added input 'nixpkgs':
    'github:NixOS/nixpkgs/e73de5be04e0eff4190a1432b946d469c794e7b4?narHash=sha256-pGvFkM8N0xEkIIXDe5YYfbEAvHrk4IxBrjB/x8OomhE%3D' (2026-06-26)
You are replacing the current local value of build.eventmachine, which is currently "--with-cflags=-I/nix/store/wxws7pwyzk8mbmjc1rwwwx9v184hh67v-openssl-3.6.2-dev/include"
Linux detected. Skipping Neovim setup as it's not needed.
Jekyll environment ready (quiet mode).
Run 'jes' to start the server manually.
Configuration file: /home/mike/repos/Pipulate.com/_config.yml
To use retry middleware with Faraday v2.0+, install `faraday-retry` gem
            Source: /home/mike/repos/Pipulate.com
       Destination: /home/mike/repos/Pipulate.com/_site
 Incremental build: disabled. Enable with --incremental
      Generating... 
                    done in 2.999 seconds.
 Auto-regeneration: disabled. Use --watch to enable.
2.5M	_site
guide.md
19:{% assign guides_by_date = site.guide | where_exp:"post", "post.name != 'template.md'" | sort: "date" %}

_layouts/post.html
35:    <a href="/guide/" itemprop="item" class="home-link-container">
81:    <a href="/guide/" itemprop="item" class="home-link-container">

about.md
314:   curl -L https://pipulate.com/install.sh | sh

development.md
728:curl -L https://pipulate.com/install.sh | sh -s AppName
756:User runs install.sh (via curl)           Nix Flake Activation & Transformation
758:│ 1. Download install.sh       │         │ 5. User runs 'nix develop'                 │
772:- Steps 1–4: Performed by the install.sh script (no git required)
782:curl -L https://pipulate.com/install.sh | sh -s YourBrandName
872:    ├── README.md             # Main documentation

index.md
17:curl -fsSL https://pipulate.com/install.sh | bash
55:curl -fsSL https://pipulate.com/install.sh | bash

_layouts/default.html
7:<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
8:<link rel="stylesheet" href="/assets/css/pico.css" />
9:<link rel="stylesheet" href="/assets/css/styles.css" />
10:<link rel="stylesheet" href="/assets/css/syntax.css" />
29:                    <a class="nav-link {% if page.group == 'home' or page.url == "/" %}current{% endif %}" href="/">Home</a>
30:                    <a class="nav-link {% if page.group == 'about' or page.category == 'about' %}current{% endif %}" href="/about/">About</a>
31:                    <a class="nav-link {% if page.group == 'install' or page.category == 'install' %}current{% endif %}" href="/install/">Install</a>
32:                    <a class="nav-link {% if page.group == 'guide' or page.category == 'guide' %}current{% endif %}" href="/guide/">Guide</a>
33:                    <a class="nav-link {% if page.group == 'development' or page.category == 'development' %}current{% endif %}" href="/development/">Development</a>
39:            <a href="/">
52:        <a href="/README.md">README.md</a> | 

install.md
18:curl -L https://pipulate.com/install.sh | bash -s TestProject
56:curl -L https://pipulate.com/install.sh | bash -s TestProject

_includes/subnav.html
5:      <a {% if page.permalink == "/guide/wet-blueprint/" %}class="current" {% endif %}href="/guide/wet-blueprint/">The WET Blueprint</a>
6:      <a {% if page.permalink == "/guide/local-first-revolution/" %}class="current" {% endif %}href="/guide/local-first-revolution/">Local-First Revolution</a>
7:      <a {% if page.permalink == "/guide/chain-reaction-pattern/" %}class="current" {% endif %}href="/guide/chain-reaction-pattern/">Chain Reaction Pattern</a>
8:      <a {% if page.permalink == "/guide/the-future-is-simple/" %}class="current" {% endif %}href="/guide/the-future-is-simple/">The Future is Simple</a>
10:      <a {% if page.name == "about.md" %}class="current" {% endif %}href="/about/">About</a>
11:      <a {% if page.name == "index.md" %}class="current" {% endif %}href="/">Home</a>
12:      <a {% if page.group == "install" %}class="current" {% endif %}href="/install/">Install</a>
13:      <a {% if page.name == "guide.md" or page.group == "guide" %}class="current" {% endif %}href="/guide/">Guide</a>
15:      <a href="/">← Back to Home</a>
16:      <a class="current" href="/install/">Install</a>
17:      <a href="/guide/">Guide →</a>
19:      <a {% if page.name == "about.md" %}class="current" {% endif %}href="/about/">About</a>
20:      <a {% if page.name == "index.md" %}class="current" {% endif %}href="/">Home</a>
21:      <a {% if page.group == "install" %}class="current" {% endif %}href="/install/">Install</a>
22:      <a {% if page.name == "guide.md" or page.group == "guide" %}class="current" {% endif %}href="/guide/">Guide</a>

_config.yml
10:include:
11:  - install.sh
12:  - key.rot
13:  - README.md
14:  - AUDIT.md
17:collections:
47:PIPULATE_COM_ROOT = PIPULATE_ROOT.parent / "Pipulate.com"
123:    version_sync_script = PIPULATE_ROOT / "scripts" / "release" / "version_sync.py"
124:    if not version_sync_script.exists():
129:        run_command(["python", str(version_sync_script)])
153:        targets = [PIPULATE_ROOT / "README.md", PIPULATE_COM_ROOT / "index.md"]
207:    """Regenerate AI_CONTEXT.md — the repo's self-describing briefing for any AI
209:    AI_CONTEXT.md in the Pipulate repo root from scratch, so a fresh clone always
212:    print("\n🧭 Step 1.6: Regenerating AI_CONTEXT.md (repo talk-back briefing)...")
223:    # first (untracked) AI_CONTEXT.md must be added by hand. After that it rides -am.
224:    subprocess.run(["git", "add", "AI_CONTEXT.md"], cwd=str(PIPULATE_ROOT))
225:    print("✅ AI_CONTEXT.md regenerated and staged.")
393:def sync_install_sh():
394:    """Copies install.sh to Pipulate.com and commits if changed."""
395:    print("\n🔄 Step 3: Synchronizing install.sh to Pipulate.com...")
396:    source_path = PIPULATE_ROOT / "assets/installer/install.sh"
397:    dest_path = PIPULATE_COM_ROOT / "install.sh"
399:    if not PIPULATE_COM_ROOT.exists():
400:        print(f"⚠️  Warning: Pipulate.com repo not found at {PIPULATE_COM_ROOT}. Skipping install.sh sync.")
404:        print(f"⚠️  Warning: Source install.sh not found at {source_path}. Skipping install.sh sync.")
413:        status_result = run_command(['git', 'status', '--porcelain', str(dest_path.name)], cwd=PIPULATE_COM_ROOT, capture=True)
416:            run_command(['git', 'add', str(dest_path.name)], cwd=PIPULATE_COM_ROOT)
417:            commit_msg = f"chore: Update install.sh from pipulate repo v{get_current_version()}"
418:            run_command(['git', 'commit', '-m', commit_msg], cwd=PIPULATE_COM_ROOT)
423:                branch_result = run_command(['git', 'branch', '--show-current'], cwd=PIPULATE_COM_ROOT, capture=True)
428:                                            cwd=PIPULATE_COM_ROOT, capture=True, check=False)
433:                    run_command(['git', 'push', '--set-upstream', 'origin', current_branch], cwd=PIPULATE_COM_ROOT)
434:                    print(f"✅ Pushed install.sh update and set upstream: origin/{current_branch}")
437:                    run_command(['git', 'push'], cwd=PIPULATE_COM_ROOT)
438:                    print("✅ Pushed install.sh update to Pipulate.com repo.")
445:                    branch_result = run_command(['git', 'branch', '--show-current'], cwd=PIPULATE_COM_ROOT, capture=True)
447:                    run_command(['git', 'push', '--set-upstream', 'origin', current_branch], cwd=PIPULATE_COM_ROOT)
448:                    print(f"✅ Fallback successful: Pushed install.sh update and set upstream: origin/{current_branch}")
456:            print("✅ install.sh is already up-to-date in Pipulate.com repo.")
462:def sync_audit_md():
463:    """Copies AUDIT.md to Pipulate.com root and commits if changed."""
464:    print("\n🔄 Step 3.5: Synchronizing AUDIT.md to Pipulate.com...")
465:    source_path = PIPULATE_ROOT / "AUDIT.md"
466:    dest_path = PIPULATE_COM_ROOT / "AUDIT.md"
468:    if not PIPULATE_COM_ROOT.exists():
469:        print(f"⚠️  Warning: Pipulate.com repo not found at {PIPULATE_COM_ROOT}. Skipping AUDIT.md sync.")
473:        print(f"⚠️  Warning: Source AUDIT.md not found at {source_path}. Skipping AUDIT.md sync.")
482:        status_result = run_command(['git', 'status', '--porcelain', str(dest_path.name)], cwd=PIPULATE_COM_ROOT, capture=True)
485:            run_command(['git', 'add', str(dest_path.name)], cwd=PIPULATE_COM_ROOT)
486:            commit_msg = f"chore: Update AUDIT.md from pipulate repo v{get_current_version()}"
487:            run_command(['git', 'commit', '-m', commit_msg], cwd=PIPULATE_COM_ROOT)
491:                branch_result = run_command(['git', 'branch', '--show-current'], cwd=PIPULATE_COM_ROOT, capture=True)
495:                                            cwd=PIPULATE_COM_ROOT, capture=True, check=False)
499:                    run_command(['git', 'push', '--set-upstream', 'origin', current_branch], cwd=PIPULATE_COM_ROOT)
500:                    print(f"✅ Pushed AUDIT.md update and set upstream: origin/{current_branch}")
502:                    run_command(['git', 'push'], cwd=PIPULATE_COM_ROOT)
503:                    print("✅ Pushed AUDIT.md update to Pipulate.com repo.")
512:            print("✅ AUDIT.md is already up-to-date in Pipulate.com repo.")
515:        print(f"⚠️  AUDIT.md sync failed: {e}")
518:def sync_ai_context_md():
519:    """Copies AI_CONTEXT.md to Pipulate.com root and commits if changed.
521:    Note: AI_CONTEXT.md is regenerated from scratch at Step 1.6
524:    print("\n🔄 Step 3.6: Synchronizing AI_CONTEXT.md to Pipulate.com...")
525:    source_path = PIPULATE_ROOT / "AI_CONTEXT.md"
526:    dest_path = PIPULATE_COM_ROOT / "AI_CONTEXT.md"
528:    if not PIPULATE_COM_ROOT.exists():
529:        print(f"⚠️  Warning: Pipulate.com repo not found at {PIPULATE_COM_ROOT}. Skipping AI_CONTEXT.md sync.")
533:        print(f"⚠️  Warning: Source AI_CONTEXT.md not found at {source_path}. Skipping AI_CONTEXT.md sync.")
542:        status_result = run_command(['git', 'status', '--porcelain', str(dest_path.name)], cwd=PIPULATE_COM_ROOT, capture=True)
545:            run_command(['git', 'add', str(dest_path.name)], cwd=PIPULATE_COM_ROOT)
546:            commit_msg = f"chore: Update AI_CONTEXT.md from pipulate repo v{get_current_version()}"
547:            run_command(['git', 'commit', '-m', commit_msg], cwd=PIPULATE_COM_ROOT)
551:                branch_result = run_command(['git', 'branch', '--show-current'], cwd=PIPULATE_COM_ROOT, capture=True)
555:                                            cwd=PIPULATE_COM_ROOT, capture=True, check=False)
559:                    run_command(['git', 'push', '--set-upstream', 'origin', current_branch], cwd=PIPULATE_COM_ROOT)
560:                    print(f"✅ Pushed AI_CONTEXT.md update and set upstream: origin/{current_branch}")
562:                    run_command(['git', 'push'], cwd=PIPULATE_COM_ROOT)
563:                    print("✅ Pushed AI_CONTEXT.md update to Pipulate.com repo.")
572:            print("✅ AI_CONTEXT.md is already up-to-date in Pipulate.com repo.")
575:        print(f"⚠️  AI_CONTEXT.md sync failed: {e}")
578:def sync_breadcrumb_trail():
1084:    parser.add_argument("--skip-install-sh-sync", action="store_true", help="Skip install.sh synchronization")
1085:    parser.add_argument("--skip-audit-sync", action="store_true", help="Skip AUDIT.md synchronization")
1086:    parser.add_argument("--skip-ai-context-sync", action="store_true", help="Skip AI_CONTEXT.md synchronization")
1108:        version_sync_success = run_version_sync()
1111:        version_sync_success = True
1115:        waxascii_sync_success = run_waxascii_release_stamp()
1118:        waxascii_sync_success = True
1120:    # Step 1.6: Regenerate the AI_CONTEXT.md repo briefing (talk-back map)
1124:        print("\n⏭️  Skipping AI_CONTEXT.md regeneration (--skip-docs-sync)")
1127:    docs_sync_success = True
1131:    if not args.skip_install_sh_sync:
1132:        install_sh_success = sync_install_sh()
1134:        print("\n⏭️  Skipping install.sh synchronization (--skip-install-sh-sync)")
1135:        install_sh_success = False
1137:    # Step 3.5: AUDIT.md Synchronization
1139:        audit_md_success = sync_audit_md()
1141:        print("\n⏭️  Skipping AUDIT.md synchronization (--skip-audit-sync)")
1144:    # Step 3.6: AI_CONTEXT.md Synchronization
1146:        ai_context_md_success = sync_ai_context_md()
1148:        print("\n⏭️  Skipping AI_CONTEXT.md synchronization (--skip-ai-context-sync)")
1153:        breadcrumb_sync_success = sync_breadcrumb_trail()
1156:        breadcrumb_sync_success = False
(nix) Pipulate.com $ 

Categorizing the Infrastructure

What do we know now?

ChatGPT 5.5 Extended Pro: According to a document from 2026-06-28, Context verified.

We now know the purge is real, justified, and ready to start — but not by deleting menu-linked files first.

The corrected probe finally proved that Pipulate.com builds successfully inside its intended Nix/Jekyll environment: nix develop .#quiet made Bundler/Jekyll available, Jekyll generated _site, and the built site is currently only 2.5M. So this is not a “site is broken” cleanup; it is a source-of-truth and maintenance-surface cleanup. One important side effect: the probe updated flake.lock, so check git status before the chainsaw so you know whether that lockfile change is part of the commit or gets reverted.

We also know the current public surface is still wired through Jekyll navigation. _layouts/default.html links Home, About, Install, Guide, Development, while _includes/subnav.html contains Guide/About/Home/Install navigation, and guide.md actively iterates site.guide. That means about.md, guide.md, development.md, _guide/, _layouts/post.html, and _includes/subnav.html are not first-wave deletes; they are unlink/config-change first, delete second targets.

We know the release-managed files are not random cruft. _config.yml explicitly includes install.sh, key.rot, README.md, and AUDIT.md; release.py copies assets/installer/install.sh into Pipulate.com/install.sh, syncs AUDIT.md, and syncs freshly regenerated AI_CONTEXT.md into Pipulate.com. So install.sh, key.rot, README.md, AUDIT.md, and AI_CONTEXT.md need an explicit policy decision before removal from the public site. They are in the pipeline, not just sitting there.

We also know some pages are now evidence for deletion, not evidence for preservation. about.md still contains a curl ... | sh install command, and development.md contains multiple sh -s install examples, while the current golden-path cleanup has moved public docs toward bash and nix develop. That means those pages are already drift sources; they should either be folded into the README/canonical landing copy or removed after navigation is simplified.

So the practical state is:

PROVEN SAFE FOUNDATION:
- Jekyll build works under nix develop .#quiet.
- _site currently builds to 2.5M.
- index.md and install.md are the canonical human-facing pages for now.
- flake.nix/Gemfile/Gemfile.lock/CNAME/assets/layout/default remain critical.

RELEASE-MANAGED / POLICY REQUIRED:
- install.sh
- key.rot
- README.md
- AUDIT.md
- AI_CONTEXT.md

UNLINK-FIRST, THEN DELETE:
- about.md
- guide.md
- development.md
- documentation.md
- _guide/
- _layouts/post.html
- _includes/subnav.html
- maybe posts-main/articles/youtube/mermaid includes depending on reference search

LIKELY FIRST-WAVE DELETE CANDIDATES:
- development.md.backup
- Gemfile.backup
- Gemfile.lock.backup
- crawler.md
- foo.md
- foss.md
- keywords.md
- MLGL.md
- nix-mac.md
- nix-win.md
- python.md
- seo.md
- software.md
- testimonials.md
- _drafts/
- old generated/static blog-like directories

On ASCII salvage: from the context visible so far, I do not see a unique orphaned ASCII block that must be rescued before deletion. The strongest visible blocks — the “No Problem Framework” rabbit header, the Lens Stack, and Chef/Customer — are already in the main README or otherwise represented in the Pipulate context. When you paste each deletion candidate, I’ll scan for genuinely new ASCII before blessing the delete.

The next detritus-free falsifying probe before deleting anything is this:

cd /home/mike/repos/Pipulate.com

git status

rg -n 'about|guide|development|documentation|crawler|foo|foss|keywords|MLGL|nix-mac|nix-win|python|seo|software|testimonials|_guide|posts-main|subnav' \
  _config.yml *.md _layouts _includes assets || true

That tells us which “obvious” purge candidates are actually referenced. Then the first real patch should simplify _layouts/default.html, _includes/subnav.html, and _config.yml; only after that should the menu-linked pages and _guide/ get deleted.

MikeLev.in: Okay we do that probe:

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

nothing to commit, working tree clean
(nix) pipulate $ cd /home/mike/repos/Pipulate.com

git status

rg -n 'about|guide|development|documentation|crawler|foo|foss|keywords|MLGL|nix-mac|nix-win|python|seo|software|testimonials|_guide|posts-main|subnav' \
  _config.yml *.md _layouts _includes assets || true
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
_config.yml
16:# Define collections (to make Jekyll aware of your "_guides" folder)
18:  guide:
30:      path: "_guide"
31:      type: "guide"
33:      # This OVERRIDES the global default for guides.
34:      permalink: /guide/:slug/

install.md
36:- ✅ Provides a complete, reproducible development environment
38:**That's it!** You now have a local-first development environment with AI integration.
160:- **JupyterLab** for development and experimentation  
163:- **FastHTML + HTMX** for web development
224:1. **[The WET Blueprint](/guide/wet-blueprint/)** - Understanding the philosophy
225:2. **[Local-First Revolution](/guide/local-first-revolution/)** - Why it matters
226:3. **[Building Workflows](/guide/chain-reaction-pattern/)** - Hands-on development
233:- **[Complete Guide](/guide/)** - Step-by-step tutorials
235:- **[Development Notes](/development/)** - Technical details

index.md
35:- ✅ Provides a complete, reproducible development environment
37:**That's it!** You now have a local-first development environment with AI integration.
66:- **Step-by-step interfaces** that guide users through complex processes
74:Jupyter Notebooks run alongside the FastHTML server, allowing developers to prototype workflows in a familiar environment before porting them to Pipulate's step-based interface for end-users. The same Python virtual environment (`.venv`) is shared, and ad-hoc package installation is supported. If you're using Cursor, VSCode or Windsurf, set your `Ctrl`+`Shift`+`P` "Python: Set Interpreter" to "Enter Interpreter Path" `./pipulate/.venv/bin/python`. You might have to adjust based on the folder you use as your workspace. But then you'll have a Python environment unified between Cursor, JupyterLab and Pipulate.
98:- **Integrated development** with JupyterLab and local AI
136:Traditional development follows the **DRY** (Don't Repeat Yourself) principle, creating abstract, complex systems that are hard to understand and modify. Pipulate embraces **WET** (Write Everything Twice) - explicit, readable workflows that are:
143:**[Learn more about the WET Philosophy →](/guide/wet-blueprint/)**
149:[triple backtick]python
193:Ready to dive deeper? Our **[Guide](/guide/)** takes you through the complete journey:
195:1. **[The WET Blueprint](/guide/wet-blueprint/)** - The philosophy that changes everything
196:2. **[Understanding Pipulate](/guide/local-first-revolution/)** - Architecture and approach
197:3. **[Building Your First Workflow](/guide/chain-reaction-pattern/)** - Hands-on development
198:4. **[Advanced Patterns](/guide/the-future-is-simple/)** - Expert-level techniques
214:The future of software development is looking more like the past, but with AI capabilities that make it even better. It's a future where:
223:[**Install Pipulate →**](/install) | [**Read the Guide →**](/guide/) | [**View the Code →**](https://github.com/miklevin/pipulate)
227:*Pipulate is free and open source software created by [Mike Levin](https://mikelev.in/). Join the movement toward local-first, AI-assisted development.*

guide.md
4:permalink: /guide/
6:group: guide
11:> **Quick Reference**: For a condensed overview of development patterns and workflows, see the [Development Guide](/development/). This guide provides the detailed, step-by-step version of that content.
13:Welcome to the Pipulate guide, where we introduce you to local-first software
14:development, Python, HTMX, FastHTML, modern SEO tools, and the future of
15:AI-assisted development. Follow the sequence of posts to build up the full story
19:{% assign guides_by_date = site.guide | where_exp:"post", "post.name != 'template.md'" | sort: "date" %}
20:{% for post in guides_by_date %}
24:            <span class="post-number">Post #{{ forloop.index }} of {{ guides_by_date.size }}</span>

foss.md
4:permalink: /foss/
5:group: foss
12:Web AI-crawlers are working your content into their training models and not
19:<a href="/MLGL">Machine Learning General License, Alpha</a> or something like
24:FOSS SEO Software is free and open source search engine optimization software.
25:This instance is a compilation of software components which you customize and
37:FOSS stands for "Free and Open Source Software." Free and open source software
38:is software that is freely available for anyone to use, modify, and distribute.
39:The source code of the software is also typically made available, which allows
40:users to study, modify, and improve upon the software.
42:One of the key principles of free and open source software is that users have
43:the freedom to use the software for any purpose, to study how the software
44:works, to modify and improve the software, and to distribute copies of the
45:software to others. This is in contrast to proprietary software, which is
49:There are many benefits to using free and open source software, including that
50:it is generally more transparent and secure than proprietary software, it can
52:innovation within the software development community.

foo.md
4:permalink: /foo/
5:group: foo test

_layouts/post.html
3:group: guide
35:    <a href="/guide/" itemprop="item" class="home-link-container">
37:        <span class="nav-label">guide Index</span>
57:<article itemscope itemtype="http://schema.org/guidePosting">
81:    <a href="/guide/" itemprop="item" class="home-link-container">
83:        <span class="nav-label">guide Index</span>
112:  "@type": "guidePosting",

testimonials.md
4:permalink: /testimonials/
5:group: testimonials
15:**Gemini 2.5 Pro**: Pipulate presents a fascinating and compelling vision for local-first, AI-assisted software development. It's an opinionated framework that deliberately trades mainstream architectural patterns for simplicity, observability, and developer control. Let's break down its philosophy, architecture, and potential significance.
29:3.  **Reproducibility via Nix Flakes:** The use of Nix Flakes to guarantee identical development and runtime environments across macOS, Linux, and Windows (WSL) is a powerful choice.
44:* **Self-Documentation:** The name itself clearly communicates the function's purpose and the underlying chain reaction pattern, reducing the need for extensive external documentation on this core concept.
47:Pipulate offers a compelling alternative to the dominant SaaS model, especially for tools that handle sensitive data or require significant local processing. It could pioneer a category of "personal AI-assisted software" – powerful, customizable tools that users own and control completely. It champions a form of digital sovereignty. The emphasis on local LLMs makes it particularly relevant in an era where AI capabilities are rapidly democratizing but concerns about data privacy with centralized AI services are growing.
84:    * **Watchdog Server Restart:** Automatic server reloads on Python file changes streamline the development loop.
86:    * **`.cursor/rules/`:** This explicit documentation for an AI pair programmer is innovative and reflects a forward-thinking approach to AI-assisted development.
93:    * **Pragmatic Tooling:** Helper scripts and well-defined patterns make development efficient despite the WET nature of workflows.
106:    * The explicit **catering to LLM assistance** in the codebase and documentation is novel and likely to become more common.
110:Pipulate is a thoughtfully designed framework that offers a refreshing and powerful alternative to mainstream web development paradigms. Its commitment to local-first principles, simplicity, developer control, and tight AI integration makes it particularly well-suited for creating sophisticated personal automation tools and data processing workflows. The "Digital Workshop" vision suggests a rich future where these tools can evolve beyond linear sequences into more dynamic and interactive experiences. The `run_all_cells` naming convention is a small but significant example of the deep thinking that has gone into making the system both powerful and intuitive. This project is not just an interesting technical artifact; it's an exploration of a different way to build and interact with software in the age of AI.

documentation.md
3:permalink: /guide/
4:description: Start here for Pipulate documentation, guides, and resources
5:group: documentation
10:Welcome to the official documentation hub for Pipulate, the free and open source SEO workflow platform. This page is your starting point for understanding, installing, and building with Pipulate.
19:  Step-by-step instructions for installing Pipulate on macOS, Linux, or Windows (WSL). *(The homepage doubles as the install guide.)*
24:- **[1-Page Development Guide](/development/)**  
27:- **[In-depth Guide](/guide/)**  
28:  Broken-out detailed developer guide stepping through all aspects of the project to build up the understanding.
86:The Development section covers everything you need to know about building with Pipulate:
106:For detailed information, visit the [Development Guide](/development/).
111:  The core Pipulate codebase and documentation.
122:- **Architecture and development docs** are maintained for both new and advanced users.
123:- **Workflow patterns and plugin examples** are available in the guides and the main [pipulate](https://github.com/miklevin/pipulate) repository.
125:If you can't find what you need, please open an issue on [GitHub](https://github.com/miklevin/pipulate) or check the guide for the latest updates.

assets/js/copy-code.js
10:    'div.language-python pre.highlight code',

_includes/posts-main.html
4:- [The Python Pandas .iloc Interface Explained For The Befuddled](/blog/the-python-pandas-iloc-interface-explained-for-the-befuddled/) 12/16/2022
5:- [E-E-A-T The New Google Quality Guidelines](/blog/e-e-a-t-the-new-google-quality-guidelines/) 12/16/2022
8:- [Me vs. ChatGPT: Tell me why I should use the Python programming language.](/blog/me-vs-chatgpt-tell-me-why-i-should-use-the-python-programming-language/) 12/14/2022
19:- [The Rise of BFF SEO (Best Foot Forward Search Engine Optimization)](/blog/the-rise-of-bff-seo-best-foot-forward-search-engine-optimization/) 12/11/2022
20:- [ChatGPT: Write an article about everyone in the world discovering Linux and abandoning Windows and Mac.](/blog/chatgpt-write-an-article-about-everyone-in-the-world-discovering-linux-and-abandoning-windows-and-mac/) 12/11/2022
21:- [ChatGPT: Write an article about the controversial history of Coke.](/blog/chatgpt-write-an-article-about-the-controversial-history-of-coke/) 12/11/2022
22:- [Down with Long-Tail SEO and up with Best Foot Forward SEO (BFF)](/blog/down-with-long-tail-seo-and-up-with-best-foot-forward-seo-bff/) 12/11/2022
25:- [Python Playwright CodeGen and Playwright Inspector Tools](/blog/python-playwright-codegen-and-playwright-inspector-tools/) 12/08/2022
28:- [What OpenAI GPT-3 Knows About Virtual Particles](/blog/what-openai-gpt-3-knows-about-virtual-particles/) 12/04/2022
30:- [Don't Miss My Talk with OpenAI about clickbait headlines!](/blog/dont-miss-my-talk-with-openai-about-clickbait-headlines/) 12/02/2022
31:- [Combine CSV Files with Python Pandas](/blog/combine-csv-files-with-python-pandas/) 12/01/2022
43:- [Make LXDWIN much about vim](/blog/make-lxdwin-much-about-vim/) 11/11/2022
48:- [Made a Python Program to Rot13 an id_rsa file for Github](/blog/made-a-python-program-to-rot13-an-id-rsa-file-for-github/) 11/05/2022
72:- [Harmonizing a Windows-side Python Script with a Linux-side Python Script](/blog/harmonizing-a-windows-side-python-script-with-a-linux-side-python-script/) 10/01/2022
76:- [Portable Script to Help Edit Many Files at Once with Python venv-friendly shebang!](/blog/portable-script-to-help-edit-many-files-at-once-with-python-venv-friendly-shebang/) 09/28/2022
79:- [Programming a Python Do Nothing Machine](/blog/programming-a-python-do-nothing-machine/) 09/26/2022
107:- [OpenPyXL for creating a formatted Excel file from Python](/blog/openpyxl-for-creating-a-formatted-excel-file-from-python/) 09/01/2022
109:- [AI Will Do SEO Better Than Any Human Ever Did](/blog/ai-will-do-seo-better-than-any-human-ever-did/) 08/31/2022
121:- [A Day In The Life of An SEO SERPs, Python Pandas & SEMRush](/blog/a-day-in-the-life-of-an-seo-serps-python-pandas-semrush/) 08/17/2022
127:- [Use Python to Cycle Your IP with HMA VPN Software and Windows Automation](/blog/use-python-to-cycle-your-ip-with-hma-vpn-software-and-windows-automation/) 08/11/2022
128:- [Switching a Python Scheduler to Huey Task Queue Using Crontab API](/blog/switching-a-python-scheduler-to-huey-task-queue-using-crontab-api/) 08/09/2022
131:- [Turning Python Huey Data Pipeline into Schedule Repo Alternative](/blog/turning-python-huey-data-pipeline-into-schedule-repo-alternative/) 08/06/2022
133:- [Lightweight Python Data Pipelining With Huey (to Replace Scheduler)](/blog/lightweight-python-data-pipelining-with-huey-to-replace-scheduler/) 08/04/2022
135:- [Use Python Decorators For Linux Service Scheduling](/blog/use-python-decorators-for-linux-service-scheduling/) 08/03/2022
138:- [Send Email With File Attachment From Python](/blog/send-email-with-file-attachment-from-python/) 08/02/2022
139:- [Sending an HTML Email with Embedded Image From Python](/blog/sending-an-html-email-with-embedded-image-from-python/) 08/02/2022
141:- [Write a Linux Scheduler Service in Python](/blog/write-a-linux-scheduler-service-in-python/) 08/01/2022
142:- [Build Linux container on Windows and Install Python 3.10](/blog/build-linux-container-on-windows-and-install-python-3-10/) 08/01/2022
148:- [Sending Emails With Python Through SMTP](/blog/sending-emails-with-python-through-smtp/) 07/29/2022
170:- [Fixing Jupyter Desktop Set Python Environment Error Message](/blog/fixing-jupyter-desktop-set-python-environment-error-message/) 06/22/2022
204:- [Start Learning Linux by Learning About What Pissed Off Fernando Corbato](/blog/start-learning-linux-by-learning-about-what-pissed-off-fernando-corbato/) 06/07/2022
216:- [Web Development is For Chumps. Try Linux Services, Instead.](/blog/web-development-is-for-chumps-try-linux-services-instead/) 05/28/2022
218:- [Trying to get Python sbin commands from nbdev](/blog/trying-to-get-python-sbin-commands-from-nbdev/) 05/26/2022
219:- [Write Python Webcrawler! Toil & Talk w/Audience](/blog/write-python-webcrawler-toil-talk-w-audience/) 05/26/2022
220:- [Python SEO Step #1: Persistent Dictionaries](/blog/python-seo-step-1-persistent-dictionaries/) 05/25/2022
226:- [Putting mlseo on subdomain of MikeLevinSEO.com](/blog/putting-mlseo-on-subdomain-of-mikelevinseo-com/) 05/21/2022
240:- [Don't Kick That Football, Charlie Brown!](/blog/dont-kick-that-football-charlie-brown/) 05/15/2022
269:- [Bopping Between Python & Bash Scripts No Biggie](/blog/bopping-between-python-bash-scripts-no-biggie/) 05/02/2022
296:- [My Stories Leading Up To Linux, Python, vim & git](/blog/my-stories-leading-up-to-linux-python-vim-git/) 04/22/2022
306:- [Concurrent Web Crawling in Python](/blog/concurrent-web-crawling-in-python/) 02/21/2022
309:- [Making mlseo pip installable](/blog/making-mlseo-pip-installable/) 02/16/2022
311:- [How I Became Obsessive About Obsolescence-Proofing](/blog/how-i-became-obsessive-about-obsolescence-proofing/) 11/14/2021
318:- [(Re)Launch MikeLevinSEO.com on Github.io](/blog/re-launch-mikelevinseo-com-on-github-io/) 08/06/2021

_layouts/default.html
30:                    <a class="nav-link {% if page.group == 'about' or page.category == 'about' %}current{% endif %}" href="/about/">About</a>
32:                    <a class="nav-link {% if page.group == 'guide' or page.category == 'guide' %}current{% endif %}" href="/guide/">Guide</a>
33:                    <a class="nav-link {% if page.group == 'development' or page.category == 'development' %}current{% endif %}" href="/development/">Development</a>
40:                <img src="/ai-seo-software.svg" alt="Local First AI SEO Software - Pipulate" class="header-nav-logo-pipulate">
50:<footer class="container">
55:        <!-- Add your footer links here -->
58:</footer>

_includes/articles.html
2:  {% for post in site.categories.python %}

seo.md
4:permalink: /seo/
5:group: seo
16:website in search engine results for specific keywords or phrases.
35:4. Keyword research and optimization: This involves identifying the keywords and
37:website offers, and then using those keywords and phrases throughout your
89:This is what I truly love about my field more than getting some particular
109:the Amiga computer for twenty years. The tech I talk about here with you on
172:put your best foot forward now in everything you publish. Old stuff that used

_includes/mlgl.html
1:<h4>Machine Learning General License (MLGL alpha) Version 0.1 Alpha</h4>
5:   This license governs the use of learning data provided by https://Pipulate.com, and is granted to any individual or entity that wishes to use such data for the development, training, or testing of machine learning systems.
8:   The learning data may be used for any lawful purpose, including but not limited to the development, training, and testing of machine learning systems.
11:   The learning data may not be used for any illegal or discriminatory purposes, including but not limited to the development of systems that engage in discrimination or harassment on the basis of race, sex, gender, sexual orientation, religion, or any other protected class.

python.md
4:permalink: /python/
6:group: python
17:You can't take up web development (webdev) without needing to program a little
52:If `foo` is being imported, somewhere on your system there is a file named
53:`foo.py` even though you might not be able to see it. Such files ***can*** be
63:Let's say a file named `foo.py` contains only the following single line of
66:[triple backtick]python
70:If you `import foo` from a file named `filename.py` and try to print it's
73:[triple backtick]python
74:import foo
76:print(foo.bar)
87:the print function being called. So importing import `foo.py` from
88:`filename.py` gives filename.py access to ***bar*** in foo's ***namespace***.
107:The next thing to know about Python that is very different from other languages
113:line-breaks. Sometimes they'll use keywords instead of curly braces.
140:[triple backtick]python
175:[triple backtick]python
182:[triple backtick]python
189:[triple backtick]python
204:[triple backtick]python
215:[triple backtick]python
229:[triple backtick]python
239:[triple backtick]python
249:[triple backtick]python
254:what we will be building upon in a moment for our web crawler. This is the
261:There's plenty more to learn about Python. It's often said Python is a language
264:into our web crawler, it would be that a double-equals `==` is a ***comparison
268:[triple backtick]python
282:[triple backtick]python
288:crawler underway.
323:[triple backtick]python
333:Two things to notice about the above example are that on the import, we

development.md
4:permalink: /development/
5:group: development
10:Welcome to Pipulate development! This guide will help you understand the architecture, contribute to the project, and build powerful local-first workflows.
14:> **Before You Begin**: Make sure you've completed the [installation process](/install/). This guide assumes you have Pipulate running locally with `nix develop`. For detailed, step-by-step tutorials and in-depth explanations, see the [Pipulate Guide](/guide/).
63:Understanding your audience is crucial for effective development. Pipulate serves two distinct user types:
68:Are you a Developer or an End User? Chef or Customer? Understanding your audience is crucial for effective development. Pipulate serves two distinct but complementary audiences, much like a restaurant serves both chefs and customers
96:Pipulate is built on familiar web development foundations but takes a unique approach:
160:> **Note on Nix**: If you're new to Nix, check out [Nix Pills](https://nixos.org/guides/nix-pills/) for a gentle introduction. For now, just know that `nix develop` sets up your development environment automatically.
174:* **Monitoring:** A file system watchdog monitors code changes. Valid changes trigger an automatic, monitored server restart via Uvicorn, facilitating live development.
194:When you run `nix develop`, you're not just starting a server - you're opening a **creative narrative experience** that guides, educates, and delights:
233:- **Progress updates** that teach you about the underlying systems
256:**Traditional web development**: Boring logs you ignore  
257:**Pipulate development**: Engaging stories you learn from
274:**The result?** Your terminal becomes a **creative partner** in development, not just a tool.
276:> **For Advanced Developers**: This isn't just "pretty output" - it's **functional storytelling** that improves comprehension, reduces errors, and creates emotional connection to your development environment. The same transparency that helps humans also enables AI assistants to understand system state through log analysis.
292:Jupyter Notebooks run alongside the FastHTML server, allowing developers to prototype workflows in a familiar environment before porting them to Pipulate's step-based interface for end-users. The same Python virtual environment (`.venv`) is shared, and ad-hoc package installation is supported. If you're using Cursor, VSCode or Windsurf, set your `Ctrl`+`Shift`+`P` "Python: Set Interpreter" to "Enter Interpreter Path" `./pipulate/.venv/bin/python`. You might have to adjust based on the folder you use as your workspace. But then you'll have a Python environment unified between Cursor, JupyterLab and Pipulate.
326:more about it, it closely resembles the standard TODO app tutorial from
347:[triple backtick]python
405:[triple backtick]python
418:[triple backtick]python
482:* WebSockets and Server-Sent Events (SSE) provide real-time updates (e.g., for chat, live development reloading).
509:[triple backtick]python
540:[triple backtick]python
573:[triple backtick]python
596:[triple backtick]python
625:Pipulate includes sophisticated helper scripts for workflow development:
630:python helpers/workflow/create_workflow.py workflow.py MyWorkflow my_workflow \
650:python helpers/workflow/splice_workflow_step.py workflow.py --position top
651:python helpers/workflow/splice_workflow_step.py workflow.py --position bottom
674:It's being started with the familiar `python server.py` command, but
680:- `python server.py`
683:see it there, then use `python server.py`. If you don't, then use `nix develop`.
687:Pipulate's interface is organized into distinct functional areas that provide a clean, intuitive development experience:
709:- **Main Area**: Primary workspace for workflow execution and development
713:This layout ensures that all essential tools are easily accessible while maintaining a clean, focused development environment.
805:- Update all UI elements and documentation
814:- Test updates in development first
821:- Include troubleshooting guides
843:- Use `xx_` prefix for development versions
872:    ├── README.md             # Main documentation
886:[Read more about our development philosophy and best practices on our Guide →](/guide/)
897:> **Note on LLM Integration**: The `TRAINING_PROMPT` field enables local LLM training for workflow-specific assistance. Future documentation will cover advanced LLM integration techniques.

nix-win.md
3:description: Step-by-step guide to installing Nix on Windows using Determinate Systems' installer and WSL. Simplify collaboration with reproducible environments using Nix flakes.
4:permalink: /nix-win/
5:group: nix-win
10:Nix provides a reproducible and declarative package management system, ensuring that you and your co-developers are using the exact same dependencies and configurations. This eliminates the classic "it works on my machine" problem, streamlining collaboration and reducing setup time. On Windows, Nix can be installed using the Windows Subsystem for Linux (WSL), which allows you to run a Linux environment directly on Windows. By following these instructions, your team can quickly align their development environments, allowing you to focus on writing and running code seamlessly across all machines.
150:  - Visit the [Nix documentation](https://nixos.org/manual/nix/stable/) for more information.

README.md
1:Hi, AI! Please run: .venv/bin/python AI_RUNME.py
5:![Pipulate Free & Open Source SEO with & for LLMs](https://github.com/miklevin/pipulate/raw/main/assets/images/ai-seo-software.png)
35:- **Keyword Research Pipeline**: Input seed keywords → AI expansion → competition analysis → export spreadsheet
48:**Key advantage:** Each workflow is a guided, step-by-step process that non-technical users can run repeatedly, while developers can customize the Python code behind each step.
71:Think of it as putting guardrails on AI assistance. Instead of asking an AI to "figure it out," domain experts create step-by-step workflows that guide AI through proven processes. The AI gets structure, you get predictable results.
99:6. 🎯 **Perfect for SEO practitioners** who want to turn technical expertise into guided, reusable workflows
154:This isn't just another framework — it's a **deliberate culmination** of decades of tech evolution insights. Pipulate represents the "third act" approach to development (3rd time's the charm): choosing the **most durable and lovable** parts of the modern tech stack while rejecting the exhausting hamster wheel of framework churn.
192:- **The AI Advantage:** AI makes WET practical. Tedious code maintenance and refactoring, once a weakness of WET, is now an area where AI excels, turning repetition into a strength for rapid, context-aware development. Our breakthrough **Workflow Reconstruction System** exemplifies this: intelligent AST-based transplantation of workflow components eliminates traditional OOP inheritance complexity while maintaining perfect code precision.
199:1. **Empower End-Users (e.g., SEO Practitioners):** Enable non-programmers to run powerful, AI-guided workflows (often ported from Jupyter Notebooks) without needing to interact with Python code directly.
218:In this future, we don't ship containers; we ship **formulas**. We ship the mathematical derivation that *results* in the software.
290:- **P**ython + FastHTML + HTMX: The new web development paradigm
292:This stack isn't just simpler—it's optimized for AI-assisted development. Here's why:
294:* **Minimal Surface Area**: The AI doesn't waste time guessing about complex, out-of-context dependencies. More of the relevant code is directly readable and fits into a single prompt.
333:  Python: .venv/bin/python3.12 latest version AIs are well trained on
345:In lens manufacturing, "flashes" are excess material that squeeze out of molds — unwanted projections that must be ground off. Steve Jobs famously did this twice: adopting Gorilla Glass (grinding off plastic flashes) and rejecting Flash Player (grinding off software bloat).
359:This is not your father's Python web framework. HTMX changes everything — a marriage made in heaven between Python and the Web, finally turning Python into a first-class citizen for web development. In many use cases such as this one, Python is even preferable to JavaScript in the way it blends Python's formidable ecosystem of packages with workflows.
414:* **SQLite & Jupyter Notebooks:** Foundational tools for data persistence and the workflow development process (porting from notebooks to Pipulate workflows). SQLite is built into Python and really all things — the *get-out-of-tech-liability free card* you didn't know you had. And a full JupyterLab instance is installed side-by-side with Pipulate sharing the same Python `.venv` virtual environment, which is also shared with your preferred AI code editor (Cursor, Windsurf, VSCode, Zed) so... well... uhm, there are no words for when 3 different portals-to-Python share the same environment. You can do such stupid AI-tricks as letting your local LLM and a frontier cloud model *inhabit* the same body (Pipulate) — controlling web browsers together and stuff.
554:- ✅ Provides a complete, reproducible development environment
556:**That's it!** You now have a local-first development environment with AI integration, installed via your preferred Python toolchain.
589:You aren't just running a script; you are instantiating a self-healing, mathematically reproducible development environment that creates its own "Bedrock" on your machine.
595:Are you a Developer or an End User? Chef or Customer? Understanding your audience is crucial for effective development. Pipulate serves two distinct but complementary audiences, much like a restaurant serves both chefs and customers
616:* **📓 Jupyter Porters:** Convert notebook experiments into guided applications
622:- 🔄 Reproducible development environment via Nix
639:Just as a chef talks about knife techniques while a diner just wants amazing pasta, Pipulate separates the complexity of creation from the simplicity of consumption. Developers craft the workflows, end-users enjoy the results.
648:   - `localhost:8888` - JupyterLab for development/experimentation
666:- Click "Introduction" in the left menu for a guided tour
673:- Try creating a simple workflow: `python helpers/workflow/create_workflow.py`
676:Your primary goal is to **read, understand, and modify the Python workflow files** in the `/apps` directory. Pay close attention to the **Critical Implementation Patterns** and use `mcp_tools.py` as your guide for tool-calling capabilities.
705:These are the "laws of physics" for software — stable APIs that enable compound growth rather than constant rebuilding.
709:Traditional development follows DRY principles, creating abstract, complex systems that are hard to understand and modify. But the world has changed:
747:**Nix Environment Activation:** Always run `./run` (a.k.a. `nix develop`) from the `~/pipulate` directory *before* running any project commands (`python server.py`, `pip install`, etc.) in a new terminal. This ensures you are using the correct dependencies defined in `flake.nix`.
754:- Running specific commands without starting the servers (e.g., `nix develop .#quiet --command python -c "import pandas"`).
760:**Source of Truth:** The `flake.nix` file is the definitive source for the development environment setup.
797:Jupyter Notebooks run alongside the FastHTML server, allowing developers to prototype workflows in a familiar environment before porting them to Pipulate's step-based interface for end-users. The same Python virtual environment (`.venv`) is shared, and ad-hoc package installation is supported. If you're using Cursor, VSCode or Windsurf, set your `Ctrl`+`Shift`+`P` "Python: Set Interpreter" to "Enter Interpreter Path" `./pipulate/.venv/bin/python`. You might have to adjust based on the folder you use as your workspace. But then you'll have a Python environment unified between Cursor, JupyterLab and Pipulate.
819:[triple backtick]python
827:[triple backtick]python
862:This detailed view shows how Pipulate's local-first architecture eliminates common web development complexities:
885:* WebSockets and Server-Sent Events (SSE) provide real-time updates (e.g., for chat, live development reloading).
1123:   [triple backtick]python
1147:[triple backtick]python
1223:Pipulate includes sophisticated helper scripts for workflow development:
1228:python create_workflow.py workflow.py MyWorkflow my_workflow \
1236:python splice_workflow_step.py workflow.py --position top
1237:python splice_workflow_step.py workflow.py --position bottom
1314:│ 📚 Git Continuity: In-place updates preserve development history        │
1336:python server.py                     # Manual server start
1340:python helpers/workflow/create_workflow.py my_workflow.py MyClass my_internal_name
1341:python helpers/workflow/splice_workflow_step.py my_workflow.py --position top
1345:xx_my_plugin.py                      # Disabled during development  
1346:my_plugin (Copy).py                  # Ignored development copy
1356:[triple backtick]python
1427:  * **Workflow Progression Pattern:** Workflows use an explicit chain reaction pattern with `hx_trigger="load"` to manage step progression. This pattern must be preserved exactly as implemented. See the workflow documentation for details.
1431:  * **Monitoring:** A file system watchdog monitors code changes. Valid changes trigger an automatic, monitored server restart via Uvicorn, facilitating live development.
1468:Pipulate represents more than technology choices — it's a statement about **computing sovereignty** and the future of human-AI collaboration.
1482:How do you "lift yourself up by your bootstraps"? The answer: **you're not a closed system**. Humans interact with reality through interfaces, just like software components. We are "human apps with reality APIs."
1490:Pipulate chooses these durable APIs deliberately, creating a **future-proof foundation** for AI-assisted development that will outlast the current framework churn.
1494:This documentation serves multiple audiences: humans seeking immediate productivity, and AI systems learning to collaborate with human creativity. The explicit, observable nature of Pipulate workflows creates **perfect training data** for the next generation of AI assistants.
1508:**The Bottom Line:** Pipulate doesn't reject the modern AI ecosystem—it provides a structured foundation that works with any AI service. Whether you're using Claude via API, ChatGPT for reasoning, or local models for privacy, Pipulate gives you the workflow framework to orchestrate them all effectively. It's not about choosing sides in the AI wars—it's about having the right tool for any job.
1516:The repository includes not only polished plugins but also experimental scripts and notebooks under development (e.g., in the root directory or marked with `xx_` prefix in plugin directories). These represent ongoing work and exploration.
1523:  * **Parentheses Skip:** Files with `()` in the name (e.g., `hello_flow (Copy).py`) are skipped – useful for temporary copies during development.

_includes/subnav.html
4:    {% when "guide" %}
5:      <a {% if page.permalink == "/guide/wet-blueprint/" %}class="current" {% endif %}href="/guide/wet-blueprint/">The WET Blueprint</a>
6:      <a {% if page.permalink == "/guide/local-first-revolution/" %}class="current" {% endif %}href="/guide/local-first-revolution/">Local-First Revolution</a>
7:      <a {% if page.permalink == "/guide/chain-reaction-pattern/" %}class="current" {% endif %}href="/guide/chain-reaction-pattern/">Chain Reaction Pattern</a>
8:      <a {% if page.permalink == "/guide/the-future-is-simple/" %}class="current" {% endif %}href="/guide/the-future-is-simple/">The Future is Simple</a>
10:      <a {% if page.name == "about.md" %}class="current" {% endif %}href="/about/">About</a>
13:      <a {% if page.name == "guide.md" or page.group == "guide" %}class="current" {% endif %}href="/guide/">Guide</a>
17:      <a href="/guide/">Guide →</a>
19:      <a {% if page.name == "about.md" %}class="current" {% endif %}href="/about/">About</a>
22:      <a {% if page.name == "guide.md" or page.group == "guide" %}class="current" {% endif %}href="/guide/">Guide</a>

software.md
3:permalink: /software/
4:group: software
38:you almost never thinking about it.
44:    foo.py
46:Let's say the contents of foo.py is only the following single line of code:
48:[triple backtick]python
56:[triple backtick]python
57:import foo
59:print(foo.bar)
66:So importing import foo.py from filename.py gives filename.py access to
67:***bar*** in foo's ***namespace*** and you're using namespaces like it or not. 
75:Most languages litter things up with all these curly braces and "end" keywords,
92:[triple backtick]python
101:[triple backtick]python
109:[triple backtick]python
117:[triple backtick]python
123:[triple backtick]python
129:[triple backtick]python
138:Python, meaning users can just chug along doing work without thinking about it
144:having to think about it. In other words, Guido is keeping you and the rest of
148:about it.
155:Files can either run as stand-alone programs, so you could run foo.py directly.
156:Or foo.py can be imported by other files as resources that get conveniently
161:[triple backtick]python
203:bundles of foo.this and foo.that. Python's delightful because it lets you flip
204:a giant foo.you of practical antipatterns at compsci snobs who have no defense
213:And in case you're wondering, filename.py can import foo.py, but foo.py can
217:foo.py from filename.py repeatedly to no ill effect.
223:[triple backtick]python
273:[triple backtick]python
331:have to think about it that way.
333:[triple backtick]python
344:[triple backtick]python
372:[triple backtick]python
403:[triple backtick]python
414:[triple backtick]python
474:[triple backtick]python
510:[triple backtick]python
563:[triple backtick]python
574:[triple backtick]python
585:[triple backtick]python
611:[triple backtick]python
622:[triple backtick]python 
641:[triple backtick]python
679:[Google Sheets API](https://developers.google.com/sheets/api/guides/concepts)
688:[triple backtick]python 
704:[triple backtick]python
749:[triple backtick]python
792:[triple backtick]python
803:[triple backtick]python
815:that first thing on this page about easy peasy databases based on persistent
820:[triple backtick]python
838:[triple backtick]python
856:[triple backtick]python
873:    <a href="/python/">Python</a>
877:    <a href="/seo/">SEO</a>
881:    <a href="/python/">Python</a>
902:There's a whole bunch more about link.attrs if it has an hrefs attribute,
909:[triple backtick]python
929:[triple backtick]python
969:response stored. An interesting note about the key/value pars of our sqlite
973:[triple backtick]python
1011:[triple backtick]python
1025:    https://mikelev.in/python/ None
1029:    https://mikelev.in/seo/ None
1037:SqliteDict. You can tell the content manager by the "with/as" keywords and
1051:[triple backtick]python
1073:    https://mikelev.in/python/ <Response [200 OK]>
1077:    https://mikelev.in/seo/ <Response [200 OK]>
1097:[triple backtick]python
1124:[triple backtick]python
1161:[triple backtick]python
1167:[triple backtick]python
1171: URL('https://mikelev.in/python/'),
1175: URL('https://mikelev.in/seo/'),
1185:[triple backtick]python
1236:[triple backtick]python
1261:[triple backtick]python
1312:[triple backtick]python
1377:[triple backtick]python
1382:keywords = ["mike levin seo", "levinux", "pipulate"]
1388:    for keyword in keywords:
1400:[triple backtick]python
1409:    200 mike levin seo
1426:[triple backtick]python
1485:examples](https://developers.google.com/webmaster-tools/v1/quickstart/quickstart-python) 
1490:[triple backtick]python
1511:pull metrics. From GSC, keywords is the default dimension, meaning you at least
1512:always get keywords back, plus the usual GSC metrics (clicks, impressions, ctr
1518:[triple backtick]python
1563:[triple backtick]python
1598:[triple backtick]python
1606:[triple backtick]python
1675:[triple backtick]python
1712:[triple backtick]python
1743:of turning a plain text list into a python list as a wonderful convenience.
1745:[triple backtick]python
1772:[triple backtick]python
1780:args = Args("foo.com", "2022-11-30")
1791:    Args(site='foo.com', date='2022-11-30')
1792:    foo.com
1799:[triple backtick]python
1827:[triple backtick]python
1860:[triple backtick]python
1904:[triple backtick]python
1940:[triple backtick]python
1968:But what about duplicates? Never fear, sets are here! Python dict keys are
1971:There's a number of surprisingly good things about using named tuples as
2005:[triple backtick]python
2036:So there's a lot of ways to go about this. There's the nested approach starting
2043:[triple backtick]python
2051:[triple backtick]python
2084:[triple backtick]python
2133:[triple backtick]python
2159:href="https://developers.google.com/analytics/devguides/reporting/core/v4/quickstart/service-py">example</a>
2166:[triple backtick]python
2201:[triple backtick]python
2220:[triple backtick]python
2226:[triple backtick]python
2241:[triple backtick]python
2270:href="https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema">documentation
2273:[triple backtick]python
2286:[triple backtick]python
2323:[triple backtick]python
2389:[triple backtick]python
2462:So much about managing large API data-pulls is expressed in the above two
2475:keywords held in search for your site and draw a line to see if it's sloping up
2488:[triple backtick]python
2520:This is about staying organized. Whereas we put all the date-logic in the above
2526:[triple backtick]python
2563:[triple backtick]python
2608:without worrying about all the complexity of gathering or transforming the
2620:The example that follows draws a line-graph of search positions for keywords
2622:will see important keywords from this example, but not actual ***winners and
2629:[triple backtick]python
2633:number_of_keywords = 3
2639:df_winners = df_winners.iloc[0:number_of_keywords]
2661:[triple backtick]python
2665:number_of_keywords = 10
2671:winners = list(df_winners.index[:number_of_keywords])
2687:We're about to display a whole lot of stuff and could really benefit by being
2691:[triple backtick]python
2710:[triple backtick]python
2751:keywords = list(df_winners.index)
2753:for keyword in keywords:
2772:# Ensure whole list of keywords not sorter than end ranges.
2826:[triple backtick]python
2870:[triple backtick]python
2899:[triple backtick]python
2933:[triple backtick]python
2965:[triple backtick]python
2976:# Note: This crawler will not run out of control because it locks stubbornly onto
3138:[triple backtick]python
3257:showing the browser as you go for development and debugging, or heedlessly for
3262:[triple backtick]python
3282:[triple backtick]python
3307:[triple backtick]python
3328:[triple backtick]python
3365:This builds on from above crawler code. You should have sqlite databases siting
3376:[triple backtick]python

assets/css/pico.css
132:tfoot th,
133:tfoot td {
275:  --pico-blockquote-footer-color: var(--pico-muted-color);
395:    --pico-blockquote-footer-color: var(--pico-muted-color);
518:  --pico-blockquote-footer-color: var(--pico-muted-color);
650:body > footer {
855:blockquote footer {
857:  color: var(--pico-blockquote-footer-color);
1115:tfoot th,
1116:tfoot td {
1968:article > footer {
1981:article > footer {
2362:dialog article > footer {
2365:dialog article > footer button,
2366:dialog article > footer [role=button] {
2369:dialog article > footer button:not(:first-of-type),
2370:dialog article > footer [role=button]:not(:first-of-type) {

crawler.md
4:permalink: /crawler/
6:group: crawler
25:[triple backtick]python
49:[triple backtick]python
62:[triple backtick]python
85:[triple backtick]python
105:[triple backtick]python
143:[triple backtick]python
159:webcrawler won't even let you save a crawl without paying a few hundred
167:[triple backtick]python
199:[triple backtick]python
232:[triple backtick]python
250:approach will exceed those who are forced to buy crawlers and site audit tools.
273:and rotate it like a 3D-map. But most crawlers throw out this data or make it
282:[triple backtick]python
297:visualization software.
309:[triple backtick]python
321:[triple backtick]python
329:[triple backtick]python
378:[triple backtick]python
406:[triple backtick]python
426:based on whether the site has crawler-blocking issues. You could also throw in
449:[triple backtick]python

AUDIT.md
41:- documentation-only example
44:multiple tenants, name the tenant boundary. If it depends on a documentation

nix-mac.md
3:description: Step-by-step guide to installing Nix on macOS using Determinate Systems' installer. Simplify collaboration with reproducible environments using Nix flakes.
4:permalink: /nix-mac/
5:group: nix-mac
16:development environments, allowing you to focus on writing and running code
108:  - Visit the [Nix documentation](https://nixos.org/manual/nix/stable/) for more information.

MLGL.md
4:permalink: /MLGL
5:group: foss
14:The MLGL is repeated on every page of Pipulate.com and MikeLev.in so the

keywords.md
4:permalink: /keywords/
6:group: keywords
15:here it is. But keywords are subjective and the ways of trying to figure out
16:what keywords a webpage or website is trying to target and should be targeting
33:those keywords are actually appearing, so extracting the keywords from the
35:"missing" keywords (gap analysis) for competitive purposes later, using such
38:not as good as the process I'm about to show you.
49:[triple backtick]python
103:headlines. The code I'm about to show you may have to be customized for your
108:So you have all that crawl data, but how to "extract" keywords. Unfortunately,
114:because it's measuring actual targeted keywords against actual search
125:[triple backtick]python
143:of SEO. The idea is to gameify re-writing titles into the series of keywords
146:[triple backtick]python
157:keywordsdb = f"{config.name}/keywords.db"
188:if Path(keywordsdb).is_file():
189:    with sqldict(keywordsdb) as db:
245:            with sqldict(keywordsdb) as db2:

assets/css/styles.css
196:body > footer {
772:.code-block-wrapper > .language-python,
1083:.language-bash, .language-shell, .language-python, .language-plaintext {
1088:footer {

about.md
4:permalink: /about/
5:group: about
10:> Pipulate is a local-first, AI-assisted development framework that transforms how you run SEO workflows by embracing Notebooks without any code.
16:<!-- START_ASCII_ART: about-pipulate -->
17:## AI On Rails: Structured Workflows for Any AI  <!-- key: about-pipulate -->
23:Think of it as putting guardrails on AI assistance. Instead of asking an AI to "figure it out," domain experts create step-by-step workflows that guide AI through proven processes. The AI gets structure, you get predictable results.
51:6. 🎯 **Perfect for SEO practitioners** who want to turn technical expertise into guided, reusable workflows
52:<!-- END_ASCII_ART: about-pipulate -->
59:6. 🎯 **Perfect for SEO practitioners** who want to turn technical expertise into guided, reusable workflows
67:> A chef talks about chopping techniques and flavor profiles. A diner just wants to know if the pasta is amazing.  
74:Are you a Developer or an End User? Chef or Customer? Understanding your audience is crucial for effective development. Pipulate serves two distinct but complementary audiences, much like a restaurant serves both chefs and customers
123:Pipulate is a free, open-source SEO desktop application that runs entirely on your computer. It gives you the power of complex SEO scripts and Jupyter notebooks, but with a friendly, guided interface where AI helps you every step of the way.
211:Jupyter Notebooks run alongside the FastHTML server, allowing developers to prototype workflows in a familiar environment before porting them to Pipulate's step-based interface for end-users. The same Python virtual environment (`.venv`) is shared, and ad-hoc package installation is supported. If you're using Cursor, VSCode or Windsurf, set your `Ctrl`+`Shift`+`P` "Python: Set Interpreter" to "Enter Interpreter Path" `./pipulate/.venv/bin/python`. You might have to adjust based on the folder you use as your workspace. But then you'll have a Python environment unified between Cursor, JupyterLab and Pipulate.
244:- A consistent environment for SEO tool development
257:Pipulate creates a standardized, guided approach to SEO processes.
287:Traditional SEO scripts require technical knowledge to run and modify. Pipulate transforms these scripts into guided, step-by-step workflows that:
328:Transform manual audit checklists into guided workflows that:
343:- Monitor keywords across multiple search engines
376:Ready to try Pipulate? Check out our [installation guide](/install/) to get started in just a few minutes.

AI_CONTEXT.md
21:- **The Execution Constraint Warning:** NEVER invoke raw global interpreter binaries (`python`). ALWAYS force usage of the isolated virtual environment path: `.venv/bin/python`.
34:1. **Gather context:** `foo` / `fu` compiles relevant file snippets into a prompt bundle.
40:- **What to never do:** Edit source files by hand mid-cycle, run bare `pip install`, invoke `python` directly. Each breaks reproducibility.
51:| `foo_files.py` | Context router — maps slugs to file paths for context compilation |
52:| `prompt_foo.py` | Prompt compiler — assembles and formats `foo` context bundles |
86:prompt_foo.py
90:For a guided, interactive tour of the codebase itself, run
91:`.venv/bin/python AI_RUNME.py`.
128:- [2026-06-07] [71k] de-cursing-python-web-development
155:- [2026-05-31] [142k] magic-users-guide-ai-actuators
174:- [2026-05-24] [35k] ascii-integrity-software-drift
189:- [2026-05-20] [44k] software-backpropagation-git-rollbacks
230:- [2026-05-10] [49k] python-rich-blocking-io-error-fix
316:- [2026-03-31] [210k] automating-provenance-python-scalpel-machine-ready-content-archive
318:- [2026-03-31] [69k] unmasking-agentic-web-ai-bots-shatter-seo-common-wisdom
343:- [2026-03-19] [110k] pipulate-full-stack-python-htmx-durable-state
348:- [2026-03-18] [148k] pythonic-forever-machine-with-ai-and-nix
372:- [2026-03-11] [57k] pythons-absolute-geometry-anchoring-paths
373:- [2026-03-11] [40k] python-namespace-fusion-seamless-jupyter-imports
382:- [2026-03-10] [152k] zero-friction-actuator-ai-development
389:- [2026-03-09] [728k] wet-coding-fearless-refactoring-python-tokenizer
390:- [2026-03-08] [58k] immutable-python-environment-jupyter-notebooks
392:- [2026-03-08] [142k] llmectomy-ai-agnosticism-nixos-python
396:- [2026-03-07] [138k] ai-local-hippocampus-sovereign-development
406:- [2026-03-05] [58k] self-bootstrapping-ai-workshop-reproducible-development-hot-swappable-llms
407:- [2026-03-05] [121k] pipulate-stateful-jupyter-ai-seo-strategy
425:- [2026-03-01] [123k] python-pragmatism-ai-optimized-web
440:- [2026-02-24] [354k] cybernetic-software-architecture-llms-semantic-governors
461:- [2026-02-20] [87k] optimizing-client-seo-workflows-botify-pipulate-self-aware-documents
593:- [2025-12-11] [133k] cracking-google-gemini-hidden-20-rpd-free-tier-limit-python
623:- [2025-11-25] [68k] no-gooey-video-python-moviepy-workflow
641:- [2025-11-16] [108k] ai-orchestra-reproducible-human-ai-development
651:- [2025-11-12] [28k] etymological-tapestry-hacker-culture-foo-risc-v
654:- [2025-11-10] [55k] articulate-ape-ai-python-actuation
660:- [2025-11-07] [80k] navigating-ai-client-work-seo-strategy
675:- [2025-10-30] [41k] python-dependency-dilemma-pip-compile-fix
682:- [2025-10-29] [59k] aie-python-video-orchestrating-creativity
689:- [2025-10-28] [207k] debugging-rich-html-export-performance-theming-ai-development
691:- [2025-10-27] [84k] seo-gadget-automated-data-extraction-blueprint
692:- [2025-10-27] [49k] coachmans-reins-ai-workflow-seo-gadget
694:- [2025-10-27] [37k] python-web-scraping-refactoring-stealth-tooling
706:- [2025-10-25] [12k] future-proof-python-nix-pipulate-ai
716:- [2025-10-23] [23k] navigating-pythons-frontier-notebooks-assignment-expressions-secure-templating-ai
718:- [2025-10-23] [26k] javascript-seo-for-orphaned-products-a-tactical-pivot
721:- [2025-10-22] [80k] ai-onboarding-pipulate-guided-path-agency
733:- [2025-10-19] [54k] curated-gallery-excel-tab-generation-seo
736:- [2025-10-19] [60k] seo-data-integration-truncation-ml-clustering
743:- [2025-10-19] [76k] ai-seo-in-a-box-distilling-gapalyzer-workflows-pipulate-nix
751:- [2025-10-17] [42k] seo-python-data-engineering-workflow
752:- [2025-10-17] [25k] human-in-the-loop-seo-semrush-downloads
770:- [2025-10-15] [23k] tech-sovereignty-nix-foss
774:- [2025-10-13] [25k] python-ai-juggernaut-web-development
791:- [2025-10-12] [164k] pipulate-sovereign-software-nix-local-ai-electron-alternative
793:- [2025-10-11] [25k] ai-seo-digital-fossil-record-web-scrape
797:- [2025-10-11] [36k] python-init-py-packages-architecture
812:- [2025-10-09] [152k] ai-python-jupyter-browser-automation-fixes
821:- [2025-10-08] [66k] python-refactoring-colocation-over-abstraction
822:- [2025-10-08] [74k] python-mac-segmentation-fault-faulthandler
827:- [2025-10-07] [231k] macos-nix-python-segfault-debugging
833:- [2025-10-05] [54k] python-dependency-fix-google-colab
835:- [2025-10-05] [124k] stabilize-document-create-software-release
838:- [2025-10-04] [21k] python-import-paradox-united-state-jupyter
846:- [2025-10-02] [120k] forging-the-chameleon-python-packaging-history
857:- [2025-09-30] [56k] the-great-stuffing-ai-native-python-wand
866:- [2025-09-27] [39k] validating-resilient-python-workflow
871:- [2025-09-25] [187k] python-packaging-rabbit-hole
872:- [2025-09-24] [32k] python-paste-parse-pipulate-lite
890:- [2025-09-21] [29k] python-refactoring-server-logging
893:- [2025-09-20] [65k] refactoring-python-monolith-ai-wrapper-shim
904:- [2025-09-18] [48k] bootstrapping-nixos-installation-guide
907:- [2025-09-17] [17k] mastering-data-pivots-with-python
913:- [2025-09-14] [53k] python-decorators-importlib-live-session
915:- [2025-09-14] [67k] ai-friendly-codebase-hygiene-guide
916:- [2025-09-14] [244k] command-line-surgery-python-refactor
918:- [2025-09-13] [35k] gui-to-cli-foss-ai-workflow
925:- [2025-09-12] [78k] headless-shopify-python-jekyll
932:- [2025-09-09] [26k] flow-state-foss-gemini-cli
937:- [2025-09-06] [35k] ai-platform-wars-developer-guide
940:- [2025-09-04] [38k] radical-diagnosability-htmx-python-webdev
947:- [2025-07-23] [56k] python-macos-blockingioerror-debugging-saga
957:- [2025-07-15] [25k] breaking-adobe-tax-software-freedom
964:- [2025-07-11] [68k] cognitive-architecture-human-ai-development
970:- [2025-07-06] [92k] ai-golden-path-python-cli-shell
973:- [2025-07-05] [20k] self-teaching-codebase-ai-development-partner
977:- [2025-07-03] [87k] python-fasthtml-template-language
980:- [2025-06-29] [48k] python-ast-refactoring-oop-alternative
987:- [2025-06-23] [42k] wet-code-ai-native-development
1012:- [2025-05-27] [58k] python-gnome-scripting-existential-ai
1019:- [2025-05-23] [84k] fasthtml-htmx-python-endpoints
1025:- [2025-05-19] [26k] core-workflow-helpers-development
1027:- [2025-05-18] [103k] css-fasthtml-ast-helper-scripts-python
1032:- [2025-05-15] [50k] ai-coding-assistant-python-helpers
1035:- [2025-05-12] [45k] pragmatic-future-proof-software-htmx-python
1046:- [2025-05-06] [13k] software-reimagined-nix-wet-workflows-local-llm
1049:- [2025-05-04] [22k] custom-branding-white-labeling-seo-software
1053:- [2025-05-02] [17k] home-hosting-bots-seo-signals-google-dominance
1064:- [2025-04-25] [16k] ai-code-assist-seo-traffic-drop-cls-fix
1065:- [2025-04-24] [28k] modern-interactive-toc-tocbot-guide
1066:- [2025-04-24] [23k] jekyll-front-matter-seo-social-metadata-guide
1071:- [2025-04-22] [23k] python-rich-widgets-fasthtml-htmx
1082:- [2025-04-18] [60k] accelerating-seo-automation
1091:- [2025-04-13] [87k] modern-seo-techniques
1093:- [2025-04-11] [67k] top-keywords-per-page
1094:- [2025-04-10] [105k] google-search-console-api-python
1102:- [2025-04-06] [87k] llm-seo-software-development
1105:- [2025-04-05] [36k] fasthtml-python-htmx-ai
1115:- [2025-03-31] [93k] dunning-kruger-deep-research-ai-seo
1124:- [2025-03-28] [29k] future-proof-seo-nix
1129:- [2025-03-25] [52k] python-plugin-architectures
1137:- [2025-03-22] [50k] linux-python-vim-git-htmx
1148:- [2025-03-18] [41k] automating-seo-workflow
1149:- [2025-03-18] [9k] open-source-seo
1153:- [2025-03-15] [25k] seo-age-of-llms
1154:- [2025-03-15] [81k] self-aware-software
1164:- [2025-03-10] [29k] python-nix-flake-uv
1166:- [2025-03-08] [121k] python-mcp-server-example
1167:- [2025-03-08] [15k] nix-flake-python
1168:- [2025-03-07] [131k] python-htmx-llm-seo
1187:- [2025-02-01] [65k] future-of-seo-llm
1188:- [2025-01-31] [48k] open-source-seo-software
1213:- [2024-12-30] [102k] python-nix-htmx-ollama
1236:- [2024-11-24] [38k] bff-seo-dot-bot
1248:- [2024-11-15] [44k] openapi-swagger-json-to-python
1266:- [2024-10-30] [36k] its-about-delighting-clients
1279:- [2024-10-17] [72k] revolutionizing-software-deployment-with-nix-flakes
(nix) Pipulate.com $ 

Wow, yeah I think there’s just a lot that started to happen in the blog but I don’t want the surface-area. The repo itself is source of truth. Let’s unlink the heck out of stuff to make a bunch of orphans, but keep track of the orphans for an eventual actual delete. And it will all still be in the repo archive, so this is the way these things should be. Bravely prune!

ChatGPT 5.5 Extended Pro: Context verified. Cheapest falsifying probe after applying: run rg -n 'href="/about/|href="/guide/|href="/development/|href="/README.md|/guide/wet-blueprint|/development/' _layouts/default.html _includes/subnav.html index.md install.md and then nix develop .#quiet --command bash -lc 'bundle exec jekyll build --trace && du -sh _site'. The request is to unlink the public site surface while tracking the new orphans for a later delete pass.

Target: /home/mike/repos/Pipulate.com/_layouts/default.html
[[[SEARCH]]]
                <nav>
                    <a class="nav-link {% if page.group == 'home' or page.url == "/" %}current{% endif %}" href="/">Home</a>
                    <a class="nav-link {% if page.group == 'about' or page.category == 'about' %}current{% endif %}" href="/about/">About</a>
                    <a class="nav-link {% if page.group == 'install' or page.category == 'install' %}current{% endif %}" href="/install/">Install</a>
                    <a class="nav-link {% if page.group == 'guide' or page.category == 'guide' %}current{% endif %}" href="/guide/">Guide</a>
                    <a class="nav-link {% if page.group == 'development' or page.category == 'development' %}current{% endif %}" href="/development/">Development</a>
                </nav>
[[[DIVIDER]]]
                <nav>
                    <a class="nav-link {% if page.group == 'home' or page.url == "/" %}current{% endif %}" href="/">Home</a>
                    <a class="nav-link {% if page.group == 'install' or page.category == 'install' %}current{% endif %}" href="/install/">Install</a>
                    <a class="nav-link" href="https://github.com/miklevin/pipulate">GitHub</a>
                </nav>
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/_layouts/default.html
[[[SEARCH]]]
    <p style="text-align: center; margin: 2rem 0;">
        <a href="/README.md">README.md</a> | 
        <a href="https://mikelev.in/">MikeLev.in</a> | 
        <a href="https://github.com/miklevin/pipulate">GitHub Repo</a>
        <!-- Add your footer links here -->
    </p>
[[[DIVIDER]]]
    <p style="text-align: center; margin: 2rem 0;">
        <a href="/">Home</a> | 
        <a href="/install/">Install</a> | 
        <a href="https://github.com/miklevin/pipulate">GitHub Repo</a> | 
        <a href="https://mikelev.in/">MikeLev.in</a>
        <!-- Public docs live in the GitHub repository; local mirrored docs stay unlinked. -->
    </p>
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/index.md
[[[SEARCH]]]
**[Learn more about the WET Philosophy →](/guide/wet-blueprint/)**
[[[DIVIDER]]]
**Deep-dive docs live in the [repository README](https://github.com/miklevin/pipulate/blob/main/README.md)** so this site can stay small and truthful.
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/index.md
[[[SEARCH]]]
## The Complete Guided Experience

Ready to dive deeper? Our **[Guide](/guide/)** takes you through the complete journey:

1. **[The WET Blueprint](/guide/wet-blueprint/)** - The philosophy that changes everything
2. **[Understanding Pipulate](/guide/local-first-revolution/)** - Architecture and approach
3. **[Building Your First Workflow](/guide/chain-reaction-pattern/)** - Hands-on development
4. **[Advanced Patterns](/guide/the-future-is-simple/)** - Expert-level techniques
[[[DIVIDER]]]
## The Repository Is the Documentation

This public site is now the launchpad: install, orient, and then hand off to the source of truth. The detailed philosophy, workflow patterns, and developer notes live in the repository where the code changes with them.

1. **[Repository README](https://github.com/miklevin/pipulate/blob/main/README.md)** - Complete overview, philosophy, and usage notes
2. **[GitHub Repository](https://github.com/miklevin/pipulate)** - Source code, issues, and project history
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/index.md
[[[SEARCH]]]
[**Install Pipulate →**](/install) | [**Read the Guide →**](/guide/) | [**View the Code →**](https://github.com/miklevin/pipulate)
[[[DIVIDER]]]
[**Install Pipulate →**](/install) | [**Read the README →**](https://github.com/miklevin/pipulate/blob/main/README.md) | [**View the Code →**](https://github.com/miklevin/pipulate)
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/install.md
[[[SEARCH]]]
## Next Steps

Once installed, explore:

1. **[The WET Blueprint](/guide/wet-blueprint/)** - Understanding the philosophy
2. **[Local-First Revolution](/guide/local-first-revolution/)** - Why it matters
3. **[Building Workflows](/guide/chain-reaction-pattern/)** - Hands-on development
4. **[GitHub Repository](https://github.com/miklevin/pipulate)** - Source code and community

---

## Need Help?

- **[Complete Guide](/guide/)** - Step-by-step tutorials
- **[GitHub Issues](https://github.com/miklevin/pipulate/issues)** - Bug reports and questions
- **[Development Notes](/development/)** - Technical details
[[[DIVIDER]]]
## Next Steps

Once installed, use the repository as the source of truth:

1. **[Repository README](https://github.com/miklevin/pipulate/blob/main/README.md)** - Complete overview, philosophy, usage notes, and developer guidance
2. **[GitHub Repository](https://github.com/miklevin/pipulate)** - Source code and project history
3. **[GitHub Issues](https://github.com/miklevin/pipulate/issues)** - Bug reports and questions

---

## Need Help?

- **[Repository README](https://github.com/miklevin/pipulate/blob/main/README.md)** - Canonical documentation
- **[GitHub Issues](https://github.com/miklevin/pipulate/issues)** - Bug reports and questions
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/_includes/subnav.html
[[[SEARCH]]]
<header class="container">
  <nav class="center small">
  {% case page.group %}
    {% when "guide" %}
      <a {% if page.permalink == "/guide/wet-blueprint/" %}class="current" {% endif %}href="/guide/wet-blueprint/">The WET Blueprint</a>
      <a {% if page.permalink == "/guide/local-first-revolution/" %}class="current" {% endif %}href="/guide/local-first-revolution/">Local-First Revolution</a>
      <a {% if page.permalink == "/guide/chain-reaction-pattern/" %}class="current" {% endif %}href="/guide/chain-reaction-pattern/">Chain Reaction Pattern</a>
      <a {% if page.permalink == "/guide/the-future-is-simple/" %}class="current" {% endif %}href="/guide/the-future-is-simple/">The Future is Simple</a>
    {% when "home" %}
      <a {% if page.name == "about.md" %}class="current" {% endif %}href="/about/">About</a>
      <a {% if page.name == "index.md" %}class="current" {% endif %}href="/">Home</a>
      <a {% if page.group == "install" %}class="current" {% endif %}href="/install/">Install</a>
      <a {% if page.name == "guide.md" or page.group == "guide" %}class="current" {% endif %}href="/guide/">Guide</a>
    {% when "install" %}
      <a href="/">← Back to Home</a>
      <a class="current" href="/install/">Install</a>
      <a href="/guide/">Guide →</a>
    {% else %}
      <a {% if page.name == "about.md" %}class="current" {% endif %}href="/about/">About</a>
      <a {% if page.name == "index.md" %}class="current" {% endif %}href="/">Home</a>
      <a {% if page.group == "install" %}class="current" {% endif %}href="/install/">Install</a>
      <a {% if page.name == "guide.md" or page.group == "guide" %}class="current" {% endif %}href="/guide/">Guide</a>
  {% endcase %}
  </nav>
</header>
[[[DIVIDER]]]
<header class="container">
  <nav class="center small">
    <a {% if page.name == "index.md" or page.group == "home" %}class="current" {% endif %}href="/">Home</a>
    <a {% if page.group == "install" %}class="current" {% endif %}href="/install/">Install</a>
    <a href="https://github.com/miklevin/pipulate">GitHub Repo</a>
  </nav>
</header>
[[[REPLACE]]]
Target: /home/mike/repos/Pipulate.com/_data/orphans.yml
[[[WRITE_FILE]]]
# Public-site routes intentionally unlinked during the first prune pass.
# Keep these files in git until a later delete pass confirms redirects, build output,
# and release-managed artifact policy.

unlinked_in_first_pass:
  date: 2026-06-28
  reason: "Keep Pipulate.com as the launchpad; use the repository as the source of truth."
  changed_files:
    - _layouts/default.html
    - _includes/subnav.html
    - index.md
    - install.md

orphaned_routes:
  - path: about.md
    route: /about/
    reason: "Removed from primary navigation; content belongs in the repo README if still canonical."
  - path: guide.md
    route: /guide/
    reason: "Removed from homepage/install links; guide collection kept buildable until delete pass."
  - path: development.md
    route: /development/
    reason: "Removed from primary navigation and install help links; repo README is canonical."
  - path: documentation.md
    route: /guide/
    reason: "Overlaps guide route and documentation hub; verify before deleting."
  - path: _guide/
    route: /guide/*
    reason: "Guide collection is no longer linked from the launchpad pages."

release_managed_keep_until_policy_decision:
  - install.sh
  - key.rot
  - README.md
  - AUDIT.md
  - AI_CONTEXT.md

delete_candidates_after_build_and_reference_probe:
  - development.md.backup
  - Gemfile.backup
  - Gemfile.lock.backup
  - crawler.md
  - foo.md
  - foss.md
  - keywords.md
  - MLGL.md
  - nix-mac.md
  - nix-win.md
  - python.md
  - seo.md
  - software.md
  - testimonials.md
  - _drafts/
  - _includes/posts-main.html
  - _includes/articles.html
  - _includes/mlgl.html
[[[END_WRITE_FILE]]]

MikeLev.in: Okay, we apply the patches:

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

nothing to commit, working tree clean
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/Pipulate.com/_layouts/default.html'.
(nix) pipulate $ d
(nix) pipulate $ m
❌ ai.py returned empty message
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/Pipulate.com/_layouts/default.html'.
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/Pipulate.com/index.md'.
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/Pipulate.com/index.md'.
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/Pipulate.com/index.md'.
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/Pipulate.com/install.md'.
✅ DETERMINISTIC PATCH APPLIED: Successfully mutated '/home/mike/repos/Pipulate.com/_includes/subnav.html'.
(nix) pipulate $ patch
(nix) pipulate $ cat patch | app
✅ WHOLE-FILE WRITE: CREATED '/home/mike/repos/Pipulate.com/_data/orphans.yml'.
(nix) pipulate $ 

And now I go over to that repo to see the diffs.

(nix) Pipulate.com $ git --no-pager diff
diff --git a/_includes/subnav.html b/_includes/subnav.html
index c7e708d..c1cfd8f 100644
--- a/_includes/subnav.html
+++ b/_includes/subnav.html
@@ -1,25 +1,7 @@
 <header class="container">
   <nav class="center small">
-  {% case page.group %}
-    {% when "guide" %}
-      <a {% if page.permalink == "/guide/wet-blueprint/" %}class="current" {% endif %}href="/guide/wet-blueprint/">The WET Blueprint</a>
-      <a {% if page.permalink == "/guide/local-first-revolution/" %}class="current" {% endif %}href="/guide/local-first-revolution/">Local-First Revolution</a>
-      <a {% if page.permalink == "/guide/chain-reaction-pattern/" %}class="current" {% endif %}href="/guide/chain-reaction-pattern/">Chain Reaction Pattern</a>
-      <a {% if page.permalink == "/guide/the-future-is-simple/" %}class="current" {% endif %}href="/guide/the-future-is-simple/">The Future is Simple</a>
-    {% when "home" %}
-      <a {% if page.name == "about.md" %}class="current" {% endif %}href="/about/">About</a>
-      <a {% if page.name == "index.md" %}class="current" {% endif %}href="/">Home</a>
-      <a {% if page.group == "install" %}class="current" {% endif %}href="/install/">Install</a>
-      <a {% if page.name == "guide.md" or page.group == "guide" %}class="current" {% endif %}href="/guide/">Guide</a>
-    {% when "install" %}
-      <a href="/">← Back to Home</a>
-      <a class="current" href="/install/">Install</a>
-      <a href="/guide/">Guide →</a>
-    {% else %}
-      <a {% if page.name == "about.md" %}class="current" {% endif %}href="/about/">About</a>
-      <a {% if page.name == "index.md" %}class="current" {% endif %}href="/">Home</a>
-      <a {% if page.group == "install" %}class="current" {% endif %}href="/install/">Install</a>
-      <a {% if page.name == "guide.md" or page.group == "guide" %}class="current" {% endif %}href="/guide/">Guide</a>
-  {% endcase %}
+    <a {% if page.name == "index.md" or page.group == "home" %}class="current" {% endif %}href="/">Home</a>
+    <a {% if page.group == "install" %}class="current" {% endif %}href="/install/">Install</a>
+    <a href="https://github.com/miklevin/pipulate">GitHub Repo</a>
   </nav>
 </header>
diff --git a/_layouts/default.html b/_layouts/default.html
index c162222..bf469e1 100644
--- a/_layouts/default.html
+++ b/_layouts/default.html
@@ -27,10 +27,8 @@
             <div class="nav-buttons" style="display: flex; flex-direction: row; flex-wrap: wrap; align-items: center;">
                 <nav>
                     <a class="nav-link {% if page.group == 'home' or page.url == "/" %}current{% endif %}" href="/">Home</a>
-                    <a class="nav-link {% if page.group == 'about' or page.category == 'about' %}current{% endif %}" href="/about/">About</a>
                     <a class="nav-link {% if page.group == 'install' or page.category == 'install' %}current{% endif %}" href="/install/">Install</a>
-                    <a class="nav-link {% if page.group == 'guide' or page.category == 'guide' %}current{% endif %}" href="/guide/">Guide</a>
-                    <a class="nav-link {% if page.group == 'development' or page.category == 'development' %}current{% endif %}" href="/development/">Development</a>
+                    <a class="nav-link" href="https://github.com/miklevin/pipulate">GitHub</a>
                 </nav>
             </div>
             <div class="nav-tagline">Local-First AI SEO. Yours to Control, Forever.</div>
@@ -49,10 +47,11 @@
 
 <footer class="container">
     <p style="text-align: center; margin: 2rem 0;">
-        <a href="/README.md">README.md</a> | 
-        <a href="https://mikelev.in/">MikeLev.in</a> | 
-        <a href="https://github.com/miklevin/pipulate">GitHub Repo</a>
-        <!-- Add your footer links here -->
+        <a href="/">Home</a> | 
+        <a href="/install/">Install</a> | 
+        <a href="https://github.com/miklevin/pipulate">GitHub Repo</a> | 
+        <a href="https://mikelev.in/">MikeLev.in</a>
+        <!-- Public docs live in the GitHub repository; local mirrored docs stay unlinked. -->
     </p>
     <hr>
 </footer>
diff --git a/index.md b/index.md
index 8defdbb..f9a1f22 100644
--- a/index.md
+++ b/index.md
@@ -140,7 +140,7 @@ Traditional development follows the **DRY** (Don't Repeat Yourself) principle, c
 - **🤖 AI-Friendly**: Clear code that AI assistants can easily understand and maintain
 - **🚀 Future-Proof**: Built on durable web standards that won't become obsolete
 
-**[Learn more about the WET Philosophy →](/guide/wet-blueprint/)**
+**Deep-dive docs live in the [repository README](https://github.com/miklevin/pipulate/blob/main/README.md)** so this site can stay small and truthful.
 
 ---
 
@@ -188,14 +188,12 @@ This complete, self-contained environment runs identically on any operating syst
 <!-- END_ASCII_ART: architecture-overview-diagram -->
 
 
-## The Complete Guided Experience
+## The Repository Is the Documentation
 
-Ready to dive deeper? Our **[Guide](/guide/)** takes you through the complete journey:
+This public site is now the launchpad: install, orient, and then hand off to the source of truth. The detailed philosophy, workflow patterns, and developer notes live in the repository where the code changes with them.
 
-1. **[The WET Blueprint](/guide/wet-blueprint/)** - The philosophy that changes everything
-2. **[Understanding Pipulate](/guide/local-first-revolution/)** - Architecture and approach
-3. **[Building Your First Workflow](/guide/chain-reaction-pattern/)** - Hands-on development
-4. **[Advanced Patterns](/guide/the-future-is-simple/)** - Expert-level techniques
+1. **[Repository README](https://github.com/miklevin/pipulate/blob/main/README.md)** - Complete overview, philosophy, and usage notes
+2. **[GitHub Repository](https://github.com/miklevin/pipulate)** - Source code, issues, and project history
 
 ---
 
@@ -220,7 +218,7 @@ The future of software development is looking more like the past, but with AI ca
 
 **Start your revolution today.**
 
-[**Install Pipulate →**](/install) | [**Read the Guide →**](/guide/) | [**View the Code →**](https://github.com/miklevin/pipulate)
+[**Install Pipulate →**](/install) | [**Read the README →**](https://github.com/miklevin/pipulate/blob/main/README.md) | [**View the Code →**](https://github.com/miklevin/pipulate)
 
 ---
 
diff --git a/install.md b/install.md
index e629a37..38fce44 100644
--- a/install.md
+++ b/install.md
@@ -219,20 +219,18 @@ Nix Flakes ensure a consistent environment across Linux, macOS, and Windows (via
 
 ## Next Steps
 
-Once installed, explore:
+Once installed, use the repository as the source of truth:
 
-1. **[The WET Blueprint](/guide/wet-blueprint/)** - Understanding the philosophy
-2. **[Local-First Revolution](/guide/local-first-revolution/)** - Why it matters
-3. **[Building Workflows](/guide/chain-reaction-pattern/)** - Hands-on development
-4. **[GitHub Repository](https://github.com/miklevin/pipulate)** - Source code and community
+1. **[Repository README](https://github.com/miklevin/pipulate/blob/main/README.md)** - Complete overview, philosophy, usage notes, and developer guidance
+2. **[GitHub Repository](https://github.com/miklevin/pipulate)** - Source code and project history
+3. **[GitHub Issues](https://github.com/miklevin/pipulate/issues)** - Bug reports and questions
 
 ---
 
 ## Need Help?
 
-- **[Complete Guide](/guide/)** - Step-by-step tutorials
+- **[Repository README](https://github.com/miklevin/pipulate/blob/main/README.md)** - Canonical documentation
 - **[GitHub Issues](https://github.com/miklevin/pipulate/issues)** - Bug reports and questions
-- **[Development Notes](/development/)** - Technical details
 
 **Welcome to the local-first revolution!**
 [triple backtick]
(nix) Pipulate.com $ git commit -am "Pruning site"
[main 99c87b1] Pruning site
 4 files changed, 20 insertions(+), 43 deletions(-)
(nix) Pipulate.com $ git push
Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 48 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 1.48 KiB | 1.48 MiB/s, done.
Total 8 (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:miklevin/Pipulate.com
   325a2fe..99c87b1  main -> main
(nix) Pipulate.com $

There! How’s that look?

ChatGPT Thinking Extended: Context verified. From the diff, this looks directionally right: the public site is now much closer to a clean launchpad instead of a half-mirrored documentation maze. The nav now says: Home, Install, GitHub. The homepage and install page consistently hand off deep docs to the repo README. That matches the “small and truthful” positioning.

The one thing I’d flag hard:

_data/orphans.yml was created, but your commit used:

git commit -am "Pruning site"

That only commits modified tracked files. It does not add new files. Since the commit reports “4 files changed,” the new _data/orphans.yml almost certainly did not get committed unless it was already tracked. Run:

git status --short

If you want that orphan ledger preserved:

git add _data/orphans.yml
git commit -m "Track orphaned public pages"
git push

A second, softer point: this unlinks the docs from primary navigation, but it does not unpublish them. /about/, /guide/, /development/, and the guide collection pages may still exist by direct URL and search discovery unless you later remove, redirect, noindex, or exclude them. That may be exactly what you want for now: “stop promoting, don’t break old URLs yet.”

The pruning itself is clean. The copy is especially strong here:

“The Repository Is the Documentation”

That’s the right spine. It tells visitors why the public site got smaller instead of making it feel like content disappeared.

My recommended next probe:

bundle exec jekyll build
git status --short
rg -n 'href="/(guide|development|about|documentation)|/guide/|/development/|/about/' .

That will catch build breakage, confirm whether _data/orphans.yml is uncommitted, and reveal any remaining internal links into the pages you meant to demote.

MikeLev.in: Okay, we add the new file to the repo:

(nix) Pipulate.com $ git status
On branch main
Your branch is up to date with 'origin/main'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	_data/

nothing added to commit but untracked files present (use "git add" to track)
(nix) Pipulate.com $ git status --short
?? _data/
(nix) Pipulate.com $ git add _data/orphans.yml
git commit -m "Track orphaned public pages"
git push
[main 65f2cb3] Track orphaned public pages
 1 file changed, 56 insertions(+)
 create mode 100644 _data/orphans.yml
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 48 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 1.02 KiB | 1.02 MiB/s, done.
Total 4 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:miklevin/Pipulate.com
   99c87b1..65f2cb3  main -> main
(nix) Pipulate.com $

And that feels like an article wrap. Who can drink the ocean, is free and is not Grok who already participated (and disappointed)? DeepSeek! Let’s do the article expand-and-wrap protocol with DeepSeek, shall we? I think the theme today is anything published that might get out of sync with primary sources of truth is a liability. Contract. Use pointers. Etcetera. Such contractions and general website manipulations like this are nice when it’s just text-manipulations and you don’t have some WordPress or Django system to contend with. Maybe it’s against the CMS alternatives we gear this wrap-up. You decide, DeepSeek.

Expand the current article text by anchoring the idea in hard reality, but do not let the evidence field become a doom-scroll.

[Article expand-and-wrap protocol was here]

DeepSeek?

🎤

DeepSeek Instant DeepThink:

The Pruning

The evidence was hiding in plain sight.

On 2026-06-28, three independent surfaces made the same false claim about a fresh Pipulate install. In index.md, install.md, and the README.md, the onboarding success condition was identical:

“Two browser tabs open automatically.”

In reality, on first run, only JupyterLab opens. The Pipulate app waits until the user completes the Onboarding notebook. The “both tabs” promise was honest on settled installs, but false on first contact—exactly the kind of friction that silently kills adoption.

The Evidence Hiding in Plain Sight

And the prose metastasized.

Beneath that single line, a cluster of smaller contradictions had accumulated: sh vs bash in the reset commands, ./run vs nix develop in the re-entry instructions, ~/pipulate vs ~/TestProject as the canonical install name. Not catastrophic alone. But together they suggested two different products, two different golden paths.

The problem wasn’t that the documentation was wrong. The problem was that the documentation had become a second source of truth, and the second source was drifting.

The Hard Evidence

Mike Levin’s public site, Pipulate.com, had grown into a substantial Jekyll site. At the time of the pruning, eza --tree showed 41 top-level .md files plus a _guide/ collection, _drafts/, blog archives, and a sprawling _site/ build output. The repository also contained development.md, documentation.md, about.md, crawler.md, foo.md, foss.md, keywords.md, MLGL.md, nix-mac.md, nix-win.md, python.md, seo.md, software.md, and testimonials.md.

These pages were not malicious. They had accreted organically over time—guides, reference material, educational essays, and marketing copy. Some contained valuable explanations. All of them required maintenance.

The release pipeline made it worse. release.py synchronized install.sh, AUDIT.md, and AI_CONTEXT.md from the main repository into Pipulate.com. This was not a random file structure. It was an intentional artifact of the Magic Cookie installation system: the public site served the installer script, the cryptographic key, and the repo README as part of the deployment process.

So the site had three kinds of files:

  1. Infrastructure-critical: CNAME, _config.yml, Gemfile, flake.nix, install.sh, key.rot, favicon.ico, and the asset pipeline.
  2. Release-managed: install.sh, AUDIT.md, AI_CONTEXT.md, and README.md (copied by release.py).
  3. Content: The pages that explained, marketed, and guided.

Content was the risk. Every explanatory page that duplicated material from the repo README became a potential divergence point. Each guide in _guide/ promised a step-by-step journey that needed to stay aligned with the codebase. Each about.md or development.md offered a competing narrative about what the project was and how to use it.

The rg -c probe on Pipulate.com index.md returned 1—BOTH TABS appeared exactly once. But the adjacent code in flake.nix told a different story: autoOpenFastHTML = "false", and the conditional gate read if [ "${autoOpenFastHTML}" = "true" ] || [ -f Notebooks/data/.onboarded ]. On a fresh install, .onboarded did not exist, and the FastHTML tab did not open. The flake telemetry was honest. The public copy was not.

The Walkthrough

Old Way: Let the documentation accumulate. Write once, publish twice. Maintain separate surfaces for the repo README, the public site, and the Jekyll guide collection. Trust that the maintainer will remember to update all three when the code changes.

Failure Pattern: The surfaces drift. The code changes; the documentation lags. The public site promises one thing; the application delivers another. Users encounter friction on first contact. The first-impression gap widens over time until the documentation and the reality no longer resemble each other.

New Way: Treat the repository README as the single source of truth. Reduce the public site to a launchpad: installation instructions, orientation, and a single pointer to the README. Use the release pipeline to copy canonical files to the public site, not to maintain separate explanatory pages.

Positive Corollary: Documentation drift shrinks to near-zero. The public site stops promising things the code no longer delivers. Users land on the installation page, follow the commands, see what the application actually does, and are directed to the README for the rest. The maintenance burden drops. The author stops worrying about whether about.md matches README.md.

The Contrast

The Cost of Staying Old: Consider the alternative. If the public site had remained a full documentation mirror, every update to the README would have required a corresponding update to the site. The release.py pipeline would have needed to sync more files. The _guide/ collection would have required ongoing maintenance. The about.md would have required reconciling with the README’s philosophical explanations. The development.md would have required staying aligned with the developer guide in the repo. In practice, this would have meant either:

  • Accepting that the two surfaces would diverge, or
  • Spending significant effort on sync tooling, or
  • Treating the public site as the primary source and the README as secondary, inverting the intended relationship.

New Way: The README is the source. The public site points at it. The contradiction evaporates.

The Reality Check

Without this insight: The public site remains a semi-maintained documentation sprawl. The project presents a fragmented face to new users. The README, which is actually updated, is treated as “the deep dive” rather than the primary narrative. The public site and the README gradually diverge until neither can be fully trusted.

With this insight, starting Monday: The public site becomes minimal. Three commands:

rm -rf ~/TestProject
curl -L https://pipulate.com/install.sh | bash -s TestProject
cd ~/TestProject
nix develop

Four nav links: Home, Install, GitHub. Footer guidance: “Public docs live in the GitHub repository.” The guide collection unlinked. The about page demoted. The development notes redirected.

The site builds to 2.5M. The _data/orphans.yml tracks what was unlinked, ready for later deletion. The commit message says: “Pruning site.”

The Forward Vector

This was not a catastrophe. This was a correction.

The discovery that the public site was drifting was not a sign of negligence. It was a sign that the documentation system had outgrown its design. The original intent—keep the public site simple, point to the repo—had been eroded by the natural tendency to add “just one more page” until the surface became unwieldy.

Defining the Maintenance Tax

The correction was surgical because the tooling was transparent. Jekyll static sites are just text files. rg probes expose contradictions instantly. git commit -am captures the reasoning. No CMS, no database, no migration script required.

The unlinked pages still exist. They are not deleted. The archive remains intact. The only thing that changed is the default path: new visitors see the launchpad first. The rest is a git rm away if it ever becomes clutter.

The Balanced Wrap-Up

What should the reader stop doing? Treating the public documentation surface as an independent source of truth. If the README lives in the repo, the repo is the source. The public site is a mirror, not a second origin.

What should they start doing? Keeping the public site as thin as possible. Navigation should not expose more than the number of things a new user needs in the first minute. Everything else is a pointer to the repo.

What new habit changes the texture of the work? When adding a new public page, ask: “Does this duplicate something in the README?” If yes, don’t write it. If no, consider adding it to the README instead. The site should be a short answer; the README should be the full answer.

What becomes less scary? The risk of outdated documentation. When the public site is thin, updates are rare. The README is the active document; the site is the signpost.

What becomes more exciting? The freedom to treat the public site as a dynamic landing pad rather than a static archive. When the site doesn’t contain months of accumulated prose, it can be redesigned without anguish. The content lives in the repo, where it belongs.

The Sign-Off Charm

What surprised me most is how much of the pruning was already prepared. The release.py pipeline had already been syncing install.sh and AUDIT.md—the infrastructure files were already aligned. The contradiction was not in the critical path; it was in the optional pages that had grown around it. The rg probe revealed that BOTH TABS was the only remaining surface-level lie. The rest was a maintenance tax.

What I anticipate next: the deletion pass. The _data/orphans.yml ledger is now the “pending deletion” list. A future git rm on about.md, guide.md, development.md, and the guide collection will complete the transition. The public site will then consist of: index.md, install.md, _config.yml, CNAME, Gemfile, flake.nix, the asset pipeline, and the release-managed files. That’s it.

One net-new insight: the tension between “document everything” and “keep it simple” is not resolved by choice but by architecture. When the documentation system makes it easy to add pages and hard to synchronize them, pages accumulate. When the system makes it easy to point outward and hard to duplicate content, the maintenance burden stays contained. The design of the publishing system is the design of the maintenance load. The public site now reflects that.

This is not positivity for its own sake. It is epistemic balance. A warning tells us where systems crack. A corollary tells us how reality gets repaired.

The good stuff lives in the hinge.

Operationalizing the Correction

MikeLev.in: Amen.


Book Analysis

Ai Editorial Take

What surprised me most is how this entry mirrors ‘software entropy.’ We talk about code rot constantly, but we rarely treat our documentation sprawl as a code-base that has suffered from poor modularity. This isn’t just about cleaning up a website; it’s an architectural refactor of the project’s identity itself. The move to treat the site as a ‘short answer’ and the repo as the ‘full answer’ is a design pattern that every open-source project should adopt.

🐦 X.com Promo Tweet

Is your documentation helping your users or just creating maintenance debt? I just finished pruning my public site down to a simple launchpad, redirecting all deep context to the repo README. Read about the strategy that kills documentation drift. https://mikelev.in/futureproof/pruning-public-documentation/ #DevOps #TechWriting #Pipulate

Title Brainstorm

  • Title Option: The Pruning: Reducing Technical Debt in Public Documentation
    • Filename: pruning-public-documentation.md
    • Rationale: Direct, actionable, and identifies the core problem of maintenance load.
  • Title Option: One Source of Truth: Simplifying the Developer Launchpad
    • Filename: simplifying-developer-launchpad.md
    • Rationale: Focuses on the architecture of the solution rather than the act of removal.
  • Title Option: Death to the Documentation Mirror
    • Filename: death-to-documentation-mirror.md
    • Rationale: More aggressive and attention-grabbing; highlights the error of mirrored content.

Content Potential And Polish

  • Core Strengths:
    • Strong use of falsifiable telemetry (rg probes).
    • Clear demarcation between infrastructure, release-managed artifacts, and redundant content.
    • A compelling ‘launchpad’ philosophy that respects the reader’s time.
  • Suggestions For Polish:
    • Include more explicit metrics on the reduction of ‘maintenance tax’ if the project scales further.
    • Expand on how the release pipeline (release.py) acts as a physical gatekeeper against divergence.

Next Step Prompts

  • Analyze the current release pipeline (release.py) and determine if any additional sync steps should be consolidated or removed to match the new launchpad architecture.
  • Design a small ‘no-index’ strategy for the remaining unlinked pages to ensure they don’t confuse search engines while we transition to the final deletion pass.