The public API of pi-napkin consists of the exported functions, interfaces, and classes from the two distill extension modules: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.
extensions/distill/distill-workspace.ts and extensions/distill/auto-setup.ts. These exports are the building blocks the extension factory uses internally, and they are individually testable — the test suite covers them extensively with both unit and integration fixtures. Use them in your own tests, automation scripts, or custom pi extensions that need to interact with napkin’s distill infrastructure without going through the full extension lifecycle.
From extensions/distill/distill-workspace.ts
createDistillWorkspace
meta.json.
Absolute path to the vault’s content root (
napkin.vault.contentPath). Used as the input for the vault hash that names the cache directory. Must be a git repo.Absolute path to the parent session
.jsonl to fork from.Absolute path of the parent pi session’s current working directory. Must be absolute and must exist on disk — throws
DistillError if either condition is violated. Pinned into the session-fork header so the distill subprocess’s Current working directory: line in the system prompt is byte-identical to the parent’s, preserving prompt-cache hits.See DistillWorkspace interface below.
DistillError if:
- vault is not a git repo (
.git/missing) - vault has no commits (HEAD unresolvable)
git worktree addfails (branch collision, dirty state, etc.)SessionManager.forkFromdoes not produce a session fileparentCwdis not absoluteparentCwddoes not exist on disk
removeDistillWorktree is called before re-throwing. The function never leaks a dangling worktree.
spawnDistillInWorktree
createDistillWorkspace, builds the distill prompt, and spawns a detached wrapper script that drives the full distill → merge → squash → push → cleanup lifecycle.
Absolute path to the vault’s content root.
Absolute path to the current session
.jsonl to fork.Parent pi session’s cwd. Used as the spawn
cwd so pi’s system prompt cwd line matches the parent’s.Optional model override in
<provider>/<id> format (e.g. "anthropic/claude-sonnet-4-6"). When absent, the wrapper uses the vault’s configured model.Hard wall-clock budget in seconds, wired into the wrapper’s
timeout(1) invocation. Derived from distill.maxDurationMinutes (default 600s). Must be a positive integer.Override for
child_process.spawn. Used by tests to capture spawn calls without starting real processes.workspace is the handle created by createDistillWorkspace. pid is the PID of the detached wrapper process (-1 if the OS did not assign a PID). The parent must not wait on this process — it is detached and survives parent exit.DistillError if workspace creation fails. Errors at this layer are unrecoverable — there is no child to clean up because the workspace was rolled back.
cleanupDistillWorkspace
cleanupStaleWorktrees
session_start.
A worktree is removed if any of the following are true:
<wt>/.napkin/distill/meta.jsonis missingmeta.json’spidis not a running process (process.kill(pid, 0)throwsESRCH)meta.json’s mtime is older thanSTALE_META_AGE_MS(STALE_WORKTREE_MINUTES * 60 * 1000= 60 minutes)
resolveCacheRoot
~/.cache when XDG_CACHE_HOME is unset. Canonicalizes vaultContentPath via fs.realpathSync before hashing so symlink variants of the same path resolve to the same cache directory.
detectDefaultBranch
git symbolic-ref refs/remotes/origin/HEAD— conventional default when a remoteoriginis configured.git symbolic-ref --short HEAD— current branch for local-only vaults.- Fallback:
"main"— matchesgit init -b mainused by auto-setup.
refs/heads/ prefix).
generateDistillBranchName
now and nonceHex are injectable for tests.
getDistillState
git worktree list --porcelain exactly once (instead of twice for separate active + unmerged calls) and git branch --list 'distill/*' once. Returns { active: [], unmerged: [] } on any error — never throws.
parseWorktreeList
git worktree list --porcelain output into a list of { path, branch } pairs. Only returns entries that have a branch line (skips detached-HEAD worktrees). The branch value is the short name with refs/heads/ stripped. Exported for unit tests.
getDistillTouchedFilesPostSquash
<startSha>..HEAD (i.e., files the distill wrote, after the wrapper’s squash-merge has landed on main). Paths are relative to the vault root.
Returns [] if startSha is undefined (pre-Phase-C2 meta files that lack the field) or if git log fails. Overlap detection is best-effort.
findDistillErrorLogForBranch
errorDir for a wrapper-emitted forensic log file matching <timestamp>-<pid>-<branchShort>.log. Returns the absolute path to the most recent matching .log file, or null when none exists. branchShort is the part after distill/ in the branch name.
Used by runDistillWith’s success path to detect wrapper failures that don’t surface as a timeout.
findDistillOutcomeForBranch
errorDir for a wrapper-emitted outcome sidecar matching <timestamp>-<pid>-<branchShort>.outcome. Returns a parsed DistillOutcome ({ outcomeClass, outcomePath, recoveryHint }) for the most recent match, or null when none exists.
A missing sidecar AND missing error log means abnormal termination (SIGKILL, OOM, set -e). The caller surfaces this as a warning.
resolveDistillErrorDir
<vault.configPath>/distill/errors/. Falls back to <vault>/.napkin/distill/errors/ if napkin’s vault resolution throws. The returned path may not exist on disk.
Interfaces from distill-workspace.ts
DistillWorkspace
Absolute path to the worktree root. This is the
cwd for the distill subprocess. Located under $XDG_CACHE_HOME/napkin-distill/<vault-hash>/<branch-suffix>/.Git branch name created for this distill, e.g.
distill/a1b2c3-1715198400.Absolute path to the forked session
.jsonl inside the worktree (<worktreePath>/.napkin/distill/session.jsonl).Absolute path to
meta.json inside the worktree (<worktreePath>/.napkin/distill/meta.json).Vault HEAD SHA at workspace-creation time. Used by per-completion overlap detection. Undefined for repos with no commits at creation time.
DistillMeta
Written to each workspace’s meta.json. Consumed by /distill-status and forensic recovery tooling.
PID of the detached wrapper shell. Initially the parent pi session’s PID; overwritten by the wrapper itself (
$$) after it installs its cleanup trap.Absolute path to the main vault (NOT the worktree).
Git branch name for this distill, e.g.
distill/a1b2c3-1715198400.ISO-8601 timestamp when the workspace was created.
Absolute path to the parent session’s
.jsonl. For traceability and forensic recovery.HEAD SHA at creation time. Absent on pre-Phase-C2 meta files — readers must tolerate
undefined.ActiveDistill
distill/* branch. alive is determined at read time via process.kill(pid, 0).
DistillOutcome
<vault>/.napkin/distill/errors/<timestamp>-<pid>-<branch>.outcome.
Machine-readable class string (first line of the sidecar). One of
merged-content, merged-local, no-content, or failed:<reason>.Absolute path to the sidecar file on disk.
Lines 2+ of the sidecar concatenated. Present only for
failed:* outcomes; null for happy-path classes.Key constants from distill-workspace.ts
| Constant | Value | Description |
|---|---|---|
GIT_SUBCOMMAND_TIMEOUT_MS | 30_000 (30 s) | Per-subcommand wall-clock ceiling for git invocations from the JS side. Shared with auto-setup.ts via re-export. |
STALE_WORKTREE_MINUTES | 60 | Minutes past which a worktree with a stale meta.json mtime is considered abandoned by cleanupStaleWorktrees. |
STALE_META_AGE_MS | STALE_WORKTREE_MINUTES * 60 * 1000 (3 600 000 ms) | Same threshold expressed in milliseconds, used at the mtime comparison site. |
From extensions/distill/auto-setup.ts
ensureVaultReadyForDistill
session_start (level: "fast") and again before every worktree-based spawn (level: "full").
{ contentPath: string; configPath: string }. For subdir-layout vaults, contentPath is the vault root and configPath is <contentPath>/.napkin/."fast" — file-only checks plus git-init if needed. Target latency ~10 ms; suitable for session_start."full" — superset of fast, plus git-state probes, config-tracking checks, cache-root writability, orphan pruning, and stale-branch deletion. Runs before worktree spawn.Optional dependency-injection seam. Fields:
probeWritable?: (dir: string) => WritableProbeResult— override write-probe (tests inject to simulate unwritable cache roots withoutchmod)now?: () => number— override wall clock (tests inject to pin stale-branch boundary cases)
initialized—trueif a newgit initran.scaffolded— vault-relative paths of files created or modified.error— set on fail-soft paths (git failures, legacy layout). Compare againstLEGACY_EMBEDDED_LAYOUT_ERRORto branch on migration.seededCommit—trueif an empty initial commit was seeded on an existing-but-empty repo.findings— structured per-invariant outcomes (seeHealthFinding).
HealthFinding
"auto-recovered" — the invariant was violated but repaired in place; surface as info and proceed."error" — user action required; abort the distill spawn.Stable string identifier for the check (e.g.
"gitignore-block-correct", "config.json-tracked").Human-readable description, suitable for notify text.
Present on
auto-recovered findings; describes what action was taken.LEGACY_EMBEDDED_LAYOUT_ERROR
SetupResult.error when auto-setup refuses to scaffold because the vault uses napkin’s legacy embedded layout (configPath === contentPath). Compare against this constant — do not pattern-match on the free-form error string.
parseManagedBlockRange
<giPath> (a .gitignore file) and locates the # BEGIN NAPKIN-DISTILL MANAGED / # END NAPKIN-DISTILL MANAGED markers. Returns { beginLine, endLine } (1-indexed, matching git check-ignore -v output) on success, or null if the file is missing, the markers are absent, or the markers are malformed.
isLineInsideBlock
true if the gitignore rule at <source>:<line> (as reported by git check-ignore -v) lives strictly between the managed-block markers. source must be ".gitignore" — rules from global ignores, parent directories, or info/exclude are always outside the block. Returns false when range is null.
parseCheckIgnoreVerbose
git check-ignore -v. The format is:
null when the line does not match the expected shape (conservative — caller should treat as outside-block).
parsePorcelainWorktreeBranches
git worktree list --porcelain output. Returns the set of short branch names (with refs/heads/ stripped) for all worktrees that have a branch line. Skips detached-HEAD and bare records. Exported for unit tests that pin the strict-parser behavior independently of git’s runtime output.
parseLiveWorktreeBranches
git worktree list --porcelain against vaultPath and returns the set of short branch names checked out in live worktrees. Used by the stale-distill-branch check to avoid deleting branches that are still in use. Returns an empty set when git worktree list fails.
walkToFirstExistingAncestor
dir until finding an existing ancestor directory. Used by the cache-root-writable probe: on a fresh box where ~/.cache/napkin-distill/ doesn’t exist yet, probing the cache root directly would always fail with ENOENT. Walking up to the first existing ancestor (typically ~/.cache) probes a directory that reflects the real writability state. Stops at the filesystem root.
probeWritable
dir is writable by writing and removing a temporary file (filename includes Date.now() plus a random suffix to avoid collisions across concurrent probes). Returns { writable: true } on success, or { writable: false, error: string } on failure — never throws.
countTrackedFiles
git ls-files. Returns the file count on success, or -1 if git ls-files fails or the vault is not a git repo. Used by the first-run notify to display a concrete count (e.g. “42 files tracked”) rather than an abstract scaffolding list.
Key constants from auto-setup.ts
| Constant | Value | Description |
|---|---|---|
BLOCK_MARKER_BEGIN | "# BEGIN NAPKIN-DISTILL MANAGED" | Begin marker for the managed .gitignore block. |
BLOCK_MARKER_END | "# END NAPKIN-DISTILL MANAGED" | End marker for the managed .gitignore block. |
BLOCK_CONTENT | readonly string[] | Canonical content of the managed block. mergeManagedBlock rewrites the bracketed region to match this verbatim on drift. Strict superset of GITIGNORE_LINES. |
GITIGNORE_LINES | readonly string[] | Deprecated. Snapshot of the v0.3.0 line-by-line .gitignore entries (no markers). Retained as a migration shim; superseded by BLOCK_CONTENT. Will be removed in a future release. |
STALE_DISTILL_BRANCH_GRACE_MS | 24 * 60 * 60 * 1000 (24 h) | Grace period before a stale distill/* branch with no live worktree is auto-deleted by the full-level health check. |
Notes
DistillError (extends Error) is thrown by workspace-layer operations in distill-workspace.ts. Use instanceof DistillError to distinguish workspace failures (bad git state, missing repo, branch collision, fork failure) from generic Error instances raised by the standard library.