Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Command Surface

Every command prikk accepts, in one place. This page is an inventory, not a tutorial — each command’s own behaviour, refusals, and worked examples live in the guide, and the Git→prikk mapping explains where the vocabulary differs from Git’s.

Every command accepts --help for its own usage:

prikk <command> --help
prikk init [path]
prikk key generate [--out <path>]
prikk key public [--seed-file <path>] [--role author|maintainer]
prikk key status [path] [--role author|maintainer] [--format json]
prikk setup [repo-path] [--author-seed-out <path>] [--maintainer-seed-out <path>]
prikk trust maintainer add --key-id ID --public-key HEX
prikk trust maintainer remove --key-id ID
prikk trust maintainer list [--format json]
prikk trust maintainer check --key-id ID [--format json]
prikk commit [--from-worktree] [--text-edits] [--ref heads/<branch>] -m <message>
prikk mv <old> <new>
prikk seal --allow-no-audit [--ref heads/<branch>]
prikk status [--format json]
prikk log [path] [--limit N] [--ref REF] [--format json]
prikk checkout --plan-only [path] [--ref <ref|block-id>]
prikk checkout --snapshot-plan [path] [--ref <ref|block-id>]
prikk checkout --snapshot-materialize [path] [--ref REF]
prikk checkout --patch-plan [path] [--ref <ref|block-id>] [--format json [--content-path <path>]...]
prikk checkout --patch-materialize [path] [--ref REF]
prikk checkout --patch-delete-plan [path] [--ref <ref|block-id>]
prikk checkout --patch-materialize-delete [path] [--ref REF]
prikk show <block-id|patch-id> [--format json]
prikk tree [path] [--ref <ref|block-id>] [--prefix <p>] [--format json]
prikk cat --path <p> [--ref <ref|block-id>] [--output <file> [--force]] [--max-bytes N] [--format json]
prikk extract (--ref <ref|block-id>|--claim <claim-id>) <dir> [--allow-full-replay] [--plan-only] [--max-bytes N] [--max-files N] [--format json]
prikk diff [path] [--from <ref|block-id>] [--to <ref|block-id>] [--path <p>]... [--format json]
prikk merge-evidence --baseline-block ID (--left-block ID|--left-ref REF) (--right-block ID|--right-ref REF) [path]
prikk merge-plan --baseline-block ID (--left-block ID|--left-ref REF) (--right-block ID|--right-ref REF) [path]
prikk merge --allow-no-audit --baseline-block ID --into REF --from REF [path]
prikk inverse-plan [path] [--ref REF]
prikk rollback-preview [path] [--ref REF]
prikk rollback-draft --append-inverse [path] [--ref REF] -m <message>
prikk rollback-draft-verify [path] [--ref REF]

prikk branch [list] [--all] [--format json]
prikk branch create heads/<name> [--from REF]
prikk branch close heads/<name>
prikk branch switch heads/<name>
prikk tag [list] [--format json]
prikk tag create tags/<name> --target <ref|block> [-m <message>]
prikk config get <key>
prikk config set <key> <value>
prikk config unset <key>
prikk config list
prikk bundle export --ref REF --output <file> [--force]
prikk bundle import --input <file> [--max-object-bytes N]
prikk bundle preview --input <file> [--ref REF] [--max-object-bytes N] [--format json]
prikk bundle verify --input <file> [--max-object-bytes N]
prikk archive export <file> [--format json]
prikk archive verify <file> [--format json]
prikk archive import <file> [--adopt] [--format json]
prikk sync summary --output <file>
prikk sync compare --summary <file>
prikk sync have <ref> --output <file>
prikk sync build <ref> --have <file> --output <file> [--force]
prikk sync accept <file> [--max-object-bytes N] [--claims-out <file>] [--force]
prikk sync pending
prikk sync seal <ref> --claim <id>
prikk sync seal <ref> --claims <file>
prikk sync tags
prikk sync adopt-tag <name>
prikk worktree-status [path] [--ref REF] [--format json]
prikk verify [path] [--stop-on-first-error] [--format json]
prikk doctor [path]
prikk doctor [path] --repair-wal-tail
prikk doctor [path] --repair-index
prikk doctor [path] --repair-pointer-index-tail
prikk doctor [path] --repair-tails [--plan-only]
prikk doctor [path] --repair-main-ref
prikk doctor [path] --rebuild-pointer-index [--plan-only]
prikk doctor [path] --discard-damaged-commits [--plan-only]
prikk doctor [path] --restore-queue-target --ref <ref> [--not-current-branch] [--plan-only]
prikk doctor [path] --recovery-list
prikk doctor [path] --recovery-restore <run id> [--plan-only]
prikk doctor [path] --recovery-clear [--plan-only]
prikk doctor [path] --cancel-import <id> [--plan-only]
prikk ref complete <ref> [--plan-only]
prikk format upgrade [path]
prikk unlock
prikk unlock --lock <path> [--yes|--force]
prikk compact --pointer-index|--received-index|--trust-policy|--all [--plan-only]
prikk compact --received-index|--trust-policy --keep-slot a|b [--plan-only]
prikk received remove <remotes/…> [--plan-only]
prikk --version

What --from and --target accept. branch create --from REF takes any published ref: a branch ref names its Block directly, and a tag ref is dereferenced through its tag object, so --from tags/v1 publishes the new branch at the Block that tag names. tag create --target takes a block id or a branch ref, and refuses a tag ref — a tag of a tag is outside the model (ref → tag object → block, one hop), and resolving it silently would make --target tags/v1 and --target that tag’s own block indistinguishable in history. The refusal names both accepted forms.

Key ids.

  • setup (when it creates a seed) and key generate --out write the new key’s id, ed25519-<first 16 hex of the public key>, beside the seed, as <role>.key-id in the key directory or <seed path>.key-id.
  • Every signer, setup and key status resolve the id the same way: PRIKK_<ROLE>_KEY_ID, else that file, else the legacy role word.
  • setup adopts the maintainer key under the resolved id and prints it.
  • key status reports key_id_source as environment, key-file or default, and a key-id file that does not hold its seed’s id as reason: key-id-file-mismatch, which refuses signing.

format upgrade is explicit, verified and one-way. A repository created by 0.44.0 or earlier is format 6; new repositories are format 7, which is format 6 plus “an object id may hold several records, the last authoritative”. prikk format upgrade takes the writer lock, runs the same verification as prikk verify and refuses — changing nothing — unless it would exit 0, then overwrites the one byte of the .prikk/FORMAT marker in place and flushes it (no rename; RFC 168 §3.3, crates/prikk-store/src/format_upgrade.rs:81-88). Nothing stored is rewritten. It is idempotent on a format-7 repository, never runs by itself, and has no inverse: prikk 0.44.0 and earlier refuse a format-7 repository at open. See Release Compatibility.

Refusals about refs and repositories.

  • A ref that does not exist. A command that reads an existing ref’s state refuses an absent name before anything else with precondition not met: ref <name> does not exist in this repository (exit 1), adding a sentence when remotes/<name> exists. That covers log and worktree-status given --ref, every checkout mode, inverse-plan, rollback-preview, rollback-draft, rollback-draft-verify, merge-evidence, merge-plan, merge --into/--from, bundle export, branch create --from, tag create --target, branch close and branch switch, which keeps its own route sentence.
  • Not refused: sync have, sync build and bundle preview answer an absent ref as a state (“none of it”), and commit --ref, seal --ref and branch create create the ref. In a fresh repository, log and worktree-status without --ref still report the unpublished current branch.
  • A received ref given to a command that cannot use one (checkout, inverse-plan, rollback-preview, bundle export, branch create --from, tag create --target) refuses with precondition not met: <name> is a received ref, naming the commands that read received refs (log, merge-evidence, merge-plan, bundle preview) and the one that takes it into a local branch (merge --from).
  • A path with no repository refuses as a precondition naming that path (“no prikk repository at ”), decided when the repository is opened, in every command that opens one.

A point: a ref or a bare block id. checkout’s read-only modes (--plan-only, --snapshot-plan, --patch-plan with or without --content-path, --patch-delete-plan) take --ref as a ref name or a bare block id, 64 lowercase hex characters, resolved by one resolver.

  • A block the repository holds resolves whether or not any ref reaches it: an older block on a branch, or a block only received history holds. Reading it adopts nothing.
  • block <id> is not in this repository and object <id> is a patch, not a block (naming the type it is) refuse as preconditions, exit 1. A --ref value that is neither a ref name (heads/…, tags/…, remotes/…) nor 64 lowercase hex is a usage error, exit 2.
  • The modes that write the worktree (--snapshot-materialize, --patch-materialize, --patch-materialize-delete) refuse a block id: writing the worktree needs a branch, because the next commit authors against one.
  • The prose header reads block: <id> for a block id and ref: <name> otherwise; --plan-only prints ref-state: <none> for a block id. patch-plan-content-v1’s ref holds the value as given, now possibly a block id; its schema version is unchanged.

Reading a point: tree. tree lists the files present at a point — a ref, or a bare block id — with each one’s mode, exact size and whether it is text or binary, by the same classification checkout --patch-plan --format json reports. It reads received refs. Without --ref it lists the current branch, and an unpublished current branch lists nothing, exit 0; an explicit absent ref refuses. --prefix filters by whole path components; the replay is whole-tree either way. --format json is tree-listing-v1. See Reading a point.

Reading a point: cat. cat writes one file’s bytes at a point, text or binary, to stdout or to --output. The content is resolved in full before anything is written, so a refusal writes nothing. --max-bytes bounds what is written — exactly, and all or nothing — but not memory, which is the shared replay’s own cost; --output refuses an existing file without --force and any path inside .prikk/, and is written to a temporary sibling then renamed into place. Binary content refuses a terminal, naming --output; text is written. A path that is not a file at the point refuses with path <p> does not exist at <point>. --format json is path-content-v1: the same fields as one tree-listing-v1 entry, and no bytes.

Writing a point into a new folder: extract. extract --ref <ref|block-id> <dir> writes every file at a point into a new directory that does not exist yet, no repository history and no worktree state, the same shape git archive | tar -x produces. <dir> must resolve outside this repository’s own root (.prikk/ included) — compared by identity, never by spelling, so a relative path, a .. that lands back inside, a symlinked parent, or a differently-cased or short-name spelling of the root on Windows all refuse just as a plain path inside the worktree does; a path outside the repository, including a sibling directory whose name merely starts with the repository’s own name, is unaffected. The reason is the same one commit --from-worktree already gives for its own worktree content: a copy left inside the repository would be picked up as worktree content by the very next commit. Unlike cat/tree, it anchors only where a writer would: the same admitted-anchor rule checkout’s own worktree writes use, never the weaker rule that lets cat and tree display any snapshot that merely decodes correctly. A remotes/... ref under a key this repository has not adopted therefore always replays fully from its own genesis; past 63 blocks with no admitted snapshot in reach, extract refuses by name, giving the distance and --allow-full-replay to read it anyway. Every file whose content has not changed since the admitted anchor streams straight from its stored blob id, nothing is held resident past the one file’s own size, and a path renamed after the anchor still streams from that same id, at its new path. The target must not already exist; a leftover <dir>.prikk-extract-tmp from an interrupted run refuses by name rather than being reused or deleted; the whole write is atomic (a temporary sibling, then one no-replace rename), and every file and directory is synced before it, so a crash never leaves a target with missing or partial content. provisional: yes|no says whether an admitted anchor was actually found and used, or the replay fell back to genesis instead.

--plan-only reports the point’s own file count, total bytes, largest file: N bytes and provisional, and writes nothing at all — no target, no temporary sibling. --max-bytes N/--max-files N refuse the whole point, by name, before the first byte is written — learning the point’s own total size this way costs what it costs (each file not already resident is read once to measure it), the same cost a real write already pays; the pre-walk’s own bound (refusing a point whose nearest admitted snapshot is unreachably far back) is the separate, cheap guard against unbounded history depth, checked before a single Patch or Blob is read, never relaxed by these flags. --format json (extract-report-v1) carries file_count, total_bytes, largest_file_bytes (the single largest entry’s own byte length, always present, the same number the text form’s largest file: N bytes line gives), provisional, written (whether this call actually wrote the target), point (the ref name or the block/claim id given, the one fixed key tree’s own tree-listing-v1/cat’s own path-content-v1 both use), point_kind ("ref", "block", or "claim"), target_block_id, and claim_parent_block_id (always present; null unless point_kind is "claim", in which case it is the claim’s own declared parent — the block its own patches would be sealed onto, not target_block_id, which is the block the claim recognizes; no command printed a claim’s parent before this round) — the same hand-rolled, schema_version-named convention those commands already use. --claim <claim-id> (mutually exclusive with --ref) previews a pending RecognitionClaim — the same id sync seal --claim takes — as if its own patches were sealed onto its own declared parent, without sealing anything: useful to inspect what a claim would produce (its size, or its content) before deciding to accept it. A claim naming more than one parent (one built from an ancestor Merge block) is refused by name — there is no field in the claim saying which parent is mainline, so this is refused rather than guessed. --claim takes the identical pre-walk bound as --ref/a bare block id: past the trusted distance from the claimed parent’s own nearest admitted checkpoint, it refuses by name unless --allow-full-replay is given. extract’s own peak memory, whole command, tracks the largest single file it actually reads (roughly twice that file’s own size, not once — see Current State), not the point’s own total content size; this holds the same way whether the point is named by --ref, a bare block id, or --claim — --claim’s own own derivation step (prepare_extract_plan_for_claim) costs a little more than the ref/block-id path’s own equivalent planning step (about 1.1–1.3x, measured), but that planning cost is small next to the one shared, dominant cost every path pays: streaming the point’s own largest file once. See Current State for the measured figures, both the planning-only comparison and the whole command. See Reading a point.

refusal-v1: a machine-readable refusal document. Under --format json, every refusal extract exits 1 for — never a usage error, exit 2, which prints no document — writes exactly one JSON document to stdout (never stderr; stderr’s own text and the exit code are entirely unchanged by this). This is a structural guarantee at extract’s own command boundary, not merely a property each refusal happens to have today: a refusal reached by a path this document’s own list does not yet name still gets a document, classified by a fallback, rather than exiting 1 under --format json with nothing on stdout.

{"schema_version": "refusal-v1", "command": "extract", "refusal": "<id>", "reason": "<id>|null", "message": "<the stderr text>"}

refusal is one of fifteen stable, kebab-case ids: over-max-bytes, over-max-files, destination-exists, destination-inside-repository, temporary-sibling-exists, point-not-found, claim-not-found, not-a-claim, claim-has-several-parents, claim-parent-not-sealed, case-collision, no-checkpoint-in-reach, not-a-repository, damaged-repository, io-failure. claim-parent-not-sealed means the claim’s own declared parent has not been sealed or received into this repository yet — seal or receive it first, then extract the claim again; this repository is not damaged. reason is set only for no-checkpoint-in-reach, naming which of three conditions the admitted-anchor search found in reach — one cause at a time: the first blocker the search meets, not necessarily the only one. Fix it and run again; the next refusal names the next cause, if any. In priority order: signer-not-adopted (a recorded checkpoint whose signer this repository has not adopted — adopt the key), not-recorded (a checkpoint in reach, never recorded by verify/seal — run verify), or no-checkpoint (none at all — --allow-full-replay). Every other id carries reason: null. message is stderr’s own text, without its leading error: prefix — the generic framing applied to every command’s own error, never part of the refusal’s own text itself; the JSON document never paraphrases it.

This is this project’s own, reusable form for a JSON refusal — refusal-v1 is extract’s own document today, but its shape (schema_version, command, refusal, reason, message) is designed as a precedent other commands adopt when next touched, not a one-off specific to extract.

Removing a received pointer: received remove. received remove <remotes/…> [--plan-only] removes one received ref’s own pointer from the received-index container — the same container bundle import/archive import write and cat/tree/checkout/extract read at a bare remotes/… point. The objects it reached are not removed, only the pointer: a stored Block or RefState nothing else reaches any more is reported by prikk verify as an unreached object, a non-blocking warning, not a failure (see verify’s own entry above). When verify also reports a damaged object, the walk cannot read past it: objects reached only through the damaged one are counted as unreached, and their publication trust is not checked, so the count can include objects that are in fact reached. verify still fails on the damage itself (verify/reachability.rs, verify/objects.rs). --plan-only reports what would be removed — the pointer’s own current target, or that there is nothing named — writing nothing, but it still takes the container’s lock for the duration of the read: the same reason compact’s own --plan-only variants do — an operator acts on what a preview reports, and the read should not go stale against a concurrent writer before it reaches the terminal; the lock is released once the read is done. A real run saves the removal to the recovery log unconditionally, naming the run id so prikk doctor --recovery-restore <id> can undo it, as long as nothing has since written to the same slot (a later, sound re-import of the identical name makes the restore refuse, rather than silently discarding it). Refuses a name that does not start with remotes/, a name with no received pointer, and a damaged received index, the same way compact --received-index already does.

Comparing: diff. diff shows what changed between two states. A bare prikk diff compares the current branch’s tip (with any queued commits) against the worktree — exactly what commit would author, so commit then diff is empty again; --from <point> compares that point against the worktree; --from and --to together compare two points, each a ref (a received ref is read, not adopted) or a bare block id. A lone --to is a usage error. Each differing path has one status: added, deleted, modified, renamed, mode or binary; a new worktree file is added, with its content, and ignored paths never appear. Text changes carry unified hunks with 3 lines of context, and a last line without a newline is marked as diff -u marks it. A rename is reported only when history (or a live prikk mv) declares one, never inferred from similar content. A binary file shows ids and sizes and never its bytes; prikk cat reads either side. A worktree path commit would refuse is named under unsupported_paths with commit’s own refusal and no content. The line diff is shortest within a work bound, above which the entry says minimal: false and its hunks, still valid, are larger. --path (repeatable, exact) filters the output, and the work is whole-tree either way. Two states that are the same give no differences and exit 0; diff writes nothing. An unsupported operation, or a blob that is missing or does not recompute, in either history fails the whole call. --format json is diff-report-v1; see Comparing.

Size bounds on incoming artifacts. RFC 158 Stage A: bundle import, bundle preview, bundle verify, sync compare, sync build and sync accept all read a file arriving from outside the repository, and every one of them refuses before reading it when its size is over the bound — on the open file’s own metadata, so nothing large is allocated to discover it is large, then enforced again while it streams, so a lying declared size is still caught. Every refusal names what was too large and its size, the bound applied (in bytes and the nearest binary unit), where that bound came from, and how to change it; none of them says “malformed” or a bundle is “persisted” data, since neither is true of an oversized but otherwise well-formed input.

Four size bounds (three overridable, the have-list fixed) and three count bounds exist — six variables in all (DC-86), none previously documented. The size bounds:

bounddefaultapplies tooverride
bundle256 MiBbundle import/preview/verifyPRIKK_BUNDLE_MAX_BYTES
sync exchange artifact256 MiBsync acceptPRIKK_EXCHANGE_MAX_BYTES
sync summary16 MiBsync comparePRIKK_SYNC_SUMMARY_MAX_BYTES
have-list64 MiBsync build --havefixed — no override exists

A bundle and a sync exchange artifact also each declare a count of objects they carry (bundles) or of patches/blobs/author keys/claims (exchange artifacts), bounded by PRIKK_BUNDLE_MAX_OBJECTS and PRIKK_EXCHANGE_MAX_OBJECTS respectively (default 100,000 each), and the sync summary’s ref count by PRIKK_SYNC_SUMMARY_MAX_REFS (default 100,000).

The per-object bound applies to bundle import, bundle preview, bundle verify and sync accept — everything that decodes objects from outside, never to a repository’s own commit, which is not bounded by default. It measures an object’s encoded size as it travels — its content plus a header — checked on the length prefix before the object’s bytes are copied or decoded. So a file of exactly the bound you set refuses: the header adds 69 bytes (measured, for text and binary files alike, and pinned by a test), so a file of N bytes needs a bound of at least N + 69. The refusal names the size it saw, which is that N + 69. Default: 256 MiB, the same as the total bundle/exchange bound, so it is inert at the defaults — it only acts once an operator lowers it. Resolved highest-precedence first: --max-object-bytes N on the command itself (on bundle import, bundle preview, bundle verify, and sync accept), else the repository’s own incoming.max-object-bytes in prikk config (not available to bundle verify, which opens no repository), else the default. The bound is never taken from the input it bounds — nothing inside a bundle or exchange artifact can raise it, and nothing an import or accept writes can reach .prikk/config.

What is still true for bundle verify and bundle preview, and for bundle import against a non-seekable input: an artifact under its bound is still read whole into memory, and decoding costs a multiple of that — measured on a bundle carrying one 255 MiB blob, at the defaults, three samples each (before bundle import streamed a regular file): bundle verify peaks at a median of 787,400 KiB (≈3.0× the bundle’s own size), bundle import at 1,570,984 KiB (≈6.0×). A pipe or FIFO input is read into memory in full, the same as this — a non-seekable input cannot be read twice, so it must be buffered before anything else happens.

bundle import against a regular file is different: it stays an open file, read and re-read in place rather than buffered, so its own memory follows the largest single object the bundle carries, not the whole bundle. bundle verify/bundle preview, and bundle import fed a pipe or FIFO, are unchanged and still carry the multiplier above.

prikk config is a repository’s own small, durable settings file at .prikk/config — never in the worktree, so a checked-out or imported file can never set one. One key = value per line; blank lines and # comments are allowed. An unknown key, a duplicate key, or an invalid value (zero, negative, non-integer) each refuse, naming the line, never a silent fall back to the default. The only key today is incoming.max-object-bytes. get/list print the effective value and its source ((default) or `.prikk/config`); set validates then writes atomically; unset restores the default. set rewrites the whole file and unset removes it, so a comment you wrote by hand — which the format allows — is dropped without a word: keep notes somewhere else. An older prikk build simply does not read this file — it has no effect on that build’s own behavior, and no error results from a repository holding one.

Exit codes. 0 — the operation succeeded and did what was asked. 1 — operational failure: verification findings, an integrity failure, a refusal, a dirty worktree. 2 — usage error: an unknown argument, a missing required flag, a duplicate flag. Graded verification results are in prikk verify --format json, not in the exit code.