Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cad0p/pi-napkin/llms.txt

Use this file to discover all available pages before exploring further.

The distill wrapper (extensions/distill/scripts/distill-wrapper.sh) reads a set of environment variables that exist solely to make integration tests deterministic. Production code never sets any of them — they are absent from normal pi-napkin installations by design. If you find one of these variables set in your environment by accident, unset it and re-run: the wrapper detects each variable’s absence and falls back to its normal production behavior automatically. This page documents them for maintainers and contributors so that a future-you grepping for one of these names can land here.
Never set any of these variables in a production environment. They bypass critical parts of the wrapper’s lifecycle (shim installation, agent invocation, cleanup traps) and will cause distill runs to behave incorrectly or incompletely. If you find one set unexpectedly, unset it immediately.

Environment Variables

VariablePurpose
NAPKIN_DISTILL_PI_BINOverride the pi binary path. Integration tests point this at a bash stub that simulates a specific agent behavior class (clean-distill, conflict-resolve-clean, agent-timeout, etc.) so the wrapper completes its full lifecycle without contacting a real LLM.
NAPKIN_DISTILL_SKIP_PI=1Skip both the napkin shim install and the pi invocation. Used by tests that pre-stage file changes manually and only want to exercise the wrapper’s lifecycle (validation, salvage, sidecar emission) without running an agent at all.
NAPKIN_DISTILL_NO_RECURSE=1Exported by the wrapper into the agent’s environment so a nested pi invocation won’t auto-distill recursively. Tests sometimes set it directly to suppress recursion when invoking the wrapper from inside another distill.
NAPKIN_DISTILL_HALT_AFTER_META=1Halt right after rewriting meta.json’s pid field to the wrapper’s pid. Lets tests inspect the updated meta without the cleanup trap wiping the worktree. Clears the EXIT trap so cleanup is skipped — the caller is responsible for tearing down the worktree afterwards.
NAPKIN_DISTILL_HALT_AFTER_SHIM=1Halt right after the per-distill napkin shim is installed at <worktree>/.napkin/distill/bin/napkin. Lets tests inspect the shim contents and PATH injection without the cleanup trap firing.
NAPKIN_DISTILL_FORCE_CLEANUP=1Force the salvage / cleanup path to run unconditionally, even on success. Used to test salvage idempotency. Unlike the HALT_AFTER_* hooks, this does not clear the EXIT trap — cleanup fires normally and tests assert on the post-cleanup filesystem state.
NAPKIN_DISTILL_TIMEOUT_KILL_GRACE_SECS=<n>Override the timeout(1) -k grace window (production default: 30 seconds) — the delay between SIGTERM and SIGKILL when the agent exceeds distill.maxDurationMinutes. Used to keep timeout tests fast; a SIGTERM-ignoring stub can be tested without waiting the full 30-second grace period.

End-to-End Test (bun run verify:e2e)

The e2e gate (scripts/verify-e2e.ts) verifies that the full production pipeline — JS-side session handler, bash wrapper subprocess, real setInterval poller — walks cleanly together. It is manual-only and is not run in CI. Cost is roughly $0.50 per LLM-driven variant. The script:
  1. Creates a fresh tmpdir vault via the real napkin init CLI.
  2. Drives the production session_start handler so auto-init’s git init, managed .gitignore block installation, and initial commit all run end-to-end.
  3. Triggers the /distill command handler with a real wrapper subprocess and a real 2-second setInterval poller.
  4. Asserts post-conditions: no conflict markers in tracked *.md, HEAD on the default branch, agent’s squash commit landed, distill branch removed, worktree removed, outcome sidecar with class merged-content, and origin/<default> advanced.
The gate also accepts --variant <name> (or --all) to exercise the full-level health-check decision points end-to-end — including a loud-error path (config-outside-block, which aborts before LLM dispatch and costs nothing) and an auto-recover path (orphaned-worktree, which prunes the orphan and then runs the full LLM-driven distill).
# Run the default healthy variant
bun run verify:e2e

# Run a specific variant
bun run verify:e2e --variant orphaned-worktree

# Run all variants
bun run verify:e2e --all
Exits 0 on PASS, 1 on FAIL.
The e2e test is the only gate that exercises the wrapper↔JS-poller seam — where worktree teardown and outcome-write race — which is invisible to a prompt-only harness or unit tests that mock the spawn. Run it after any change to the wrapper, the poller loop in index.ts, or the workspace lifecycle in distill-workspace.ts.

Available Test Stubs

The stub scripts live in extensions/distill/test-fixtures/agent-stubs/. Each is a self-contained bash script that reads state from NAPKIN_STUB_* environment variables and produces the filesystem effects of a specific agent behavior class, without contacting a real LLM. Point the wrapper at a stub via NAPKIN_DISTILL_PI_BIN=<path-to-stub>.

Behavior class stubs

StubSimulated behaviorExpected outcome
clean-distill.shCommits one file directly on the default branchmerged-content
no-distill.shExits 0 without producing any commitsno-content
conflict-resolve-clean.shCommits to the distill branch, merges the default branch with a conflict, resolves it cleanly, then squashesmerged-content
conflict-leave-markers.shCommits a file containing all three conflict marker types to the default branchfailed:markers-after-agent-exit
agent-crashes.shExits non-zero with diagnostic output on stderrfailed:agent-exit-nonzero
agent-timeout.shSleeps past maxDurationSecs (tests pass a very low budget)failed:agent-timeout
push-fail-merged-local.shCommits without pushing while an origin remote existsmerged-local
push-fail-pull-merge-success.shFirst push fails because origin advanced; agent recovers via git pull --no-rebase and re-pushesmerged-content
pushed-success.shFull happy path: commits and pushes to origin (requires test-side origin setup)merged-content
multiple-commits-on-main.shCommits two or more times directly on the default branch (squash-invariant violation, accepted)merged-content
squash-skipped.shCommits to the distill branch but never squashes to the default branchno-content

Race-window scaffolding stubs

These two stubs are used by wrapper-invariant.test.ts to widen the [agent-exit, worktree-removed] time window so the test can snapshot the outcome sidecar at the exact moment the worktree disappears.
StubBehavior
step10-race.shHappy-path commit + squash, then sleep 0.5 to widen the cleanup window
salvage-race.shCommits a file with conflict markers in vault *.md, then sleep 0.5

Build docs developers (and LLMs) love