# Client workflows

For every command, option, default, and exit code, see the
[client CLI reference](/docs/reference/cli/scribe.md). This guide explains how to use those
commands together and what happens to your working files.

Use `scribe -h` for commands grouped by workflow and common options, then
`scribe COMMAND --help` for a command's complete options and subcommands.

All command-line tools support global `--json`; see [JSON output for automation](/docs/json-output.md) for results, errors, and exit codes.

New here? Start with the [minimal-server tutorial](/docs/tutorials/01-minimal-server.md),
then follow the [deployment series](/docs/tutorials.md) as your setup grows.

```
scribe [global options] <command> [options] [paths]
```

Commands that operate on a workspace find it by walking up from the current directory.
Paths may be relative to the current directory or absolute; they must lie
inside the workspace.

## Choose a collaboration workflow

| Task | Start here |
| --- | --- |
| Download source without all art assets | [Views and profiles](/docs/workspace-views.md) |
| Assemble independently versioned game, engine, and art repositories | [Repository mounts](/docs/repository-mounts.md) |
| Publish parent and child work with recoverable pin updates | [Project workflows](/docs/project-workflows.md) and the [daily walkthrough](/docs/tutorials/work-with-mounts.md) |
| Bring one fix to another branch | [Cherry-pick](/docs/cherry-pick.md) |
| Merge with a source-only checkout | [Partial-view integration](/docs/partial-view-merging.md) |
| Find old working copies or release abandoned locks from another computer | [Workspace administration](/docs/workspace-administration.md) |
| Review a branch integration in the browser | [Proposals](/docs/proposals.md) |

## Global options

The [global option reference](/docs/reference/cli/scribe.md#global-options) lists connection,
trust, compression, ignore, JSON, and diagnostic switches. Their precedence and
saved settings are explained under [defaults and configuration](/docs/reference/cli/scribe.md#defaults-and-configuration).

## Client diagnostics

Run `scribe diagnostics` for an offline report of versions, effective endpoints,
configuration sources, login status, and client paths. Use `scribe diagnostics
--json` for structured output. It works outside a workspace too, preserves local
state, and omits credentials. See the [diagnostics reference](/docs/reference/cli/scribe.md#diagnostics)
for fields and inspection failures.

## Exit codes

Use the [client exit-code reference](/docs/reference/cli/scribe.md#output-and-exit-codes) and
[JSON output guide](/docs/json-output.md) when scripting commands. An unknown submit
outcome retains its pending state; rerun submit to resolve it.

## Connect to an authenticated server

An authenticated server is a `tls://` endpoint: a storage server or a gateway,
with a separate auth service people sign in at. Connecting takes three things,
each saved in your per-user configuration root (never in a workspace): trust in
the server's certificate authority, trust in auth's, and a login session. The
[authentication tutorial](/docs/tutorials/02-add-auth.md#6-log-in-as-alice-and-give-bob-repository-access)
walks through them with two users; every option is in the [client
reference](/docs/reference/cli/scribe.md#login).

Every `tls://` endpoint requires TLS 1.3 with the hybrid post-quantum key
exchange `X25519MLKEM768`. A proxy or load balancer that terminates TLS and
cannot offer it breaks the connection; pass TLS through to Scribe instead. The
client authenticates with mutual TLS: it presents a short-lived access
certificate for a device key that never leaves the machine, so nothing a
storage server or gateway receives can be replayed. See
[identity, device keys, and certificates](/docs/auth.md#identity-device-keys-and-certificates).

### Trust a server

Scribe servers use a private certificate authority (CA), so your client has to
be told which CA to trust for each server, once. The first connection to an
unknown server makes a TLS-only discovery handshake, sends nothing, and shows
the CA's fingerprint:

```text
scribe: untrusted server tls://127.0.0.1:7447; CA fingerprint BLAKE3:bec9ed90312d4b568ddb7752e5a26e6e80789e9b724b23b20a25a5a86e1c82f9. Run scribe --server tls://127.0.0.1:7447 trust add interactively, or supply --trust-fingerprint BLAKE3:bec9ed90312d4b568ddb7752e5a26e6e80789e9b724b23b20a25a5a86e1c82f9 after verifying it
```

Compare it with the fingerprint your administrator gives you through a channel
you trust (the `certs` commands print it when they create the CA), then approve
it interactively (type **yes**) or name it:

```sh
scribe --server tls://127.0.0.1:7447 trust add
scribe --server tls://127.0.0.1:7447 --trust-fingerprint BLAKE3:bec9ed90312d4b568ddb7752e5a26e6e80789e9b724b23b20a25a5a86e1c82f9 trust add
```

```text
tls://127.0.0.1:7447
CA fingerprint: BLAKE3:bec9ed90312d4b568ddb7752e5a26e6e80789e9b724b23b20a25a5a86e1c82f9
Trust directory: /home/you/.config/scribe/trust
```

Approve the auth service the same way (its CA is a different one), or give its
CA file at login with `--auth-ca`. This is trust on first use: someone who
controls that first connection could present their own CA, which the
fingerprint comparison exposes. Approval is scoped to the host name and port,
so a gateway and a storage server need one each even under the same CA. The
fingerprint is public: CI can pass `--trust-fingerprint` on its first command,
or `--tls-ca FILE` (`SCRIBE_TLS_CA`) to use a CA file instead of remembered
trust. Commands with `--json` or without a terminal never prompt.

`scribe --server URL trust show` lists the CAs saved for a server without
connecting. Trust lives in the configuration root's `trust/` directory:
`~/Library/Application Support/Scribe` on macOS, `%LOCALAPPDATA%\Scribe` on
Windows, `$XDG_CONFIG_HOME/scribe` or `~/.config/scribe` on Linux, or
`--config-dir` (`SCRIBE_CONFIG_DIR`). Records hold public CA certificates only;
a corrupt record fails closed. The desktop apps share it.

### Log in

```sh
scribe --server tls://scribe.example.com:7557 login alice --auth tls://auth.example.com:7443
```

```text
Logged in as alice; session valid for 90 days.
```

The password prompt is on stderr with input hidden; `--password-file FILE` is
for automation, and a password is never a command-line argument. Login creates
a device key on this computer and sends only its public key with the password;
auth answers with a credential certificate for the session, 90 days by default.
Later commands use it to fetch an hour-long access certificate and renew it on
their own. The session belongs to the server it was made for: a gateway and a
storage server each need a login. Logging in again replaces the session;
`scribe logout` revokes it at auth and deletes the key; `scribe whoami` shows
the saved login without contacting anything.

For repeated use, set defaults in your shell; explicit arguments override
them, and inside a workspace commands use its recorded server:

```sh
export SCRIBE_SERVER='tls://scribe.example.com:7557'
export SCRIBE_AUTH='tls://auth.example.com:7443'
export SCRIBE_USERNAME='alice'
scribe login
```

```powershell
$env:SCRIBE_SERVER = 'tls://scribe.example.com:7557'
$env:SCRIBE_AUTH = 'tls://auth.example.com:7443'
$env:SCRIBE_USERNAME = 'alice'
scribe login
```

A login whose answer never arrived is kept: rerun the same `scribe login` to
resolve it without a second session, or `scribe login --discard` when the
address or username was wrong. Session internals and lifetime settings are in
[user login and session lifetime](/docs/auth.md#user-login-and-session-lifetime).

### Sign in as administrator

A session never administers auth, even an auth administrator's. To administer
for a while, sign in as administrator; it writes a short-lived administrator
credential file instead of saving a session, and needs no storage server:

```sh
scribe login alice --admin --auth tls://auth.example.com:7443 --out admin.credential
```

```text
Signed in as alice (auth administrator); administrator credential saved to admin.credential (valid for 8 hours, until 2026-09-28T18:04:11.512Z). Use it with scribe-auth --admin-credential admin.credential or load it on the web administration page.
```

The username, password and trust arguments are those of `scribe login`. The
file lives for the hours auth is configured with (8 by default), is created
private to your account, and an existing file is never replaced. Use it with
[`scribe-auth --admin-credential FILE`](/docs/reference/cli/scribe-auth.md) or load
it on the web administration page, which can also sign you in with your
username and password directly. For automation or `scribe-ctl`, create a
long-lived one with `scribe-auth credentials create`. A sign-in whose answer
never arrived is finished by rerunning the same command, never issuing a second
credential; `scribe login --admin --discard --out FILE` forgets it. See
[administrator sign-in](/docs/auth.md#administrator-sign-in).

### Machine credentials

CI machines, build agents, and other unattended clients use a credential file
instead of a login. An auth administrator creates it for a service account with
[`scribe-auth credentials create --service`](/docs/reference/cli/scribe-auth.md#credentials-create);
the file holds the auth endpoint, the auth CA, the machine's device key, and
its credential certificate. Select it per command or per environment:

```sh
scribe --credential-file /etc/scribe/ci.credential sync
SCRIBE_CREDENTIAL_FILE=/etc/scribe/ci.credential scribe submit -m "Nightly build"
```

The file is never stored in workspace state, and `--anonymous` ignores
`SCRIBE_CREDENTIAL_FILE`. On Unix it must be readable only by its owner (mode
0600) in a directory other accounts cannot write; a file that others can read
is refused with `... is accessible to other accounts (mode 644); run chmod 600
on it`. On Windows a file whose access list allows another account more than
reading its attributes is refused the same way, naming that account's SID. The
access certificate fetched for it is cached under `credentials/` in the
configuration root. See [credential files](/docs/auth.md#credential-files).

### When a server's CA changes

Operators replace a CA in one of two ways ([certificates and
credentials](/docs/manual/certificates.md)), and the client handles each on its own
terms.

**A rotation with a handover** needs nothing from you. During an overlap the
server presents its new certificate with a handover certificate: the new CA,
signed by the old one you trust. The client saves the new CA beside the old
one without asking, since nothing is believed that your trusted CA did not
vouch for; once the server no longer presents the old chain, the client drops
the old CA. `trust show` marks a CA learned this way:

```text
tls://scribe.example.com:7557
CA fingerprint: BLAKE3:26e411692a534f4d68414ab4226585414fa41a489382371fd9bc8be07f90bd7b
CA fingerprint: BLAKE3:20d8727bad2779ab4954cf4b373324c14f1facc72ef4ac01999ca8ee871f98b7 (from a CA handover by BLAKE3:26e411692a534f4d68414ab4226585414fa41a489382371fd9bc8be07f90bd7b)
```

**Any other change**, or a rotation you did not connect during, is refused
before anything is sent, and saved trust is left unchanged:

```text
scribe: The CA of tls://127.0.0.1:7447 changed.
  trusted: BLAKE3:bec9ed90312d4b568ddb7752e5a26e6e80789e9b724b23b20a25a5a86e1c82f9
  now:     BLAKE3:2462fcd2d18e01d3ecc2c0d88d152dc1f77ecccc52332b7debb6c36c6501dfec
If your administrator confirms this change, run: scribe --server tls://127.0.0.1:7447 trust replace
```

Confirm the new fingerprint with your administrator, then replace the saved
CAs, interactively (type `replace`) or by naming the new fingerprint:

```sh
scribe --server tls://127.0.0.1:7447 trust replace
scribe --server tls://127.0.0.1:7447 --trust-fingerprint BLAKE3:2462fcd2d18e01d3ecc2c0d88d152dc1f77ecccc52332b7debb6c36c6501dfec trust replace
```

```text
Replaced saved trust for tls://127.0.0.1:7447
CA fingerprint: BLAKE3:2462fcd2d18e01d3ecc2c0d88d152dc1f77ecccc52332b7debb6c36c6501dfec
```

`trust replace` presents no credential and leaves a server that still verifies
alone. The sign-in service is handled the same way: when you approved it with
`trust add`, the message names its own address; when you logged in with
`--auth-ca`, your session keeps a copy of that CA, which follows handovers too,
and a changed one is replaced from a new file the administrator gives you
(checked against the sign-in service first), once for each server you have a
session for:

```sh
scribe --server tls://scribe.example.com:7557 trust replace --auth-ca ./new-auth-ca.pem
```

```text
Replaced the sign-in CA saved for tls://scribe.example.com:7557 (tls://auth.example.com:7443)
```

A credential file's auth CA is part of the file: a handover is followed while
a command runs, and any other change needs a new file from the administrator.
With `--tls-ca`, point it at the new CA file.

The macOS and Windows apps raise the same check wherever they contact a server
and show **Server Certificate Changed** with the trusted fingerprints beside
the one now presented; **Replace Trust** does what `trust replace` does, pinned
to the fingerprint shown ([macOS](https://github.com/Flying-Rat/scribe/blob/main/apps/macos/README.md#server-certificate-changed),
[Windows](https://github.com/Flying-Rat/scribe/blob/main/apps/windows/README.md#connecting-a-workspace)).

To remove a server you no longer use, forget its saved trust: the whole
record, every CA it holds. Name one of the fingerprints `trust show` lists:

```sh
scribe --server tls://old-host.example.com:7447 --trust-fingerprint BLAKE3:… trust forget
```

The next connection to that server asks for trust again. There is no command
that drops a single CA from a record: a handover drops the old CA by itself,
and `trust replace` replaces them all. A record from an older Scribe version
cannot be used; the message names `trust forget`, which then needs no
fingerprint.

### Desktop links

A desktop `scribe://open` link from a web page may not name a plaintext
endpoint on the network: a `tcp://` server or an `http://` web address is
accepted only for `localhost` or a loopback address, and otherwise refused
with, for example, `a link cannot name a plaintext (tcp://) server on the
network; use tls://`. See [opening in the desktop app](/docs/web-interface.md#opening-in-the-desktop-app-scribeopen).

## Repositories

```sh
scribe repo create <name> [--storage-compression lz4] # letters, digits, '-', '_', '.'; not starting with '.'; ≤ 128 bytes
scribe repo list                   # only repositories you may read on an authenticated server
scribe repo info <name|id>
```

Creating a repository on an authenticated server requires namespace
administration. Choose its permanent storage policy at creation; see
[compression](/docs/client-usage.md#compression) for the separate storage and transfer settings.

Through a [gateway](/docs/gateway-usage.md#namespaces) that serves several auth
namespaces, name repositories as `<namespace>/<name>` — `scribe repo create
studio/game`, `scribe workspace create studio/game ./game` — and `repo list`
reports names in that form. A bare name is accepted only where the gateway has
a single or default namespace; the error otherwise lists the namespaces to
choose from. Existing workspaces are unaffected: they hold the repository ID.

## Workspaces

```sh
scribe workspace create <repo> <directory> [--profile NAME] [--view PREFIX]... [--author NAME] [--readonly]
scribe workspace info
scribe workspace profiles
scribe workspace view show
scribe workspace view set --profile programmer
scribe workspace view add Art/Characters
scribe workspace view remove Art/Characters
```

The directory must not already contain a workspace. `--view` selects inclusion
prefixes (for example `--view Assets/Characters --view Source`); paths outside
the view are neither synced nor addable. Change an existing selection with
`workspace view set/add/remove`; share named selections through a versioned
`.scribe/config.toml` file. View changes are recoverable and protect local work;
see [workspace views and profiles](/docs/workspace-views.md) for the format, local
overrides, and cancellation. `--author` defaults to the logged-in user when a
session is saved for that server, then `SCRIBE_AUTHOR`, then the OS user name;
on an authenticated server the signed-in user is the recorded author regardless.
`--readonly` marks unopened tracked files read-only: `edit` clears the
attribute; `revert`, `sync`, and `submit` restore it.

### Manage registrations from another computer

```sh
scribe workspace list --all-repos
scribe workspace list --repo Game --everyone
scribe workspace remove WORKSPACE_ID --repo Game
```

These commands need a server connection, but no local working copy. With auth,
listing defaults to your registrations; `--everyone` requires Maintainer.
Owners with repository read access can remove their own registrations;
Maintainers can remove others. Without auth, any connected client can list and
remove registrations. The same rules apply through a gateway.

Removal permanently retires that workspace ID and refuses held locks. It
preserves local files and accepted history, but the old identity cannot submit
new work. See [workspace administration](/docs/workspace-administration.md) for the
Game/Engine example, explicit lock release, retries, and the boundary between
remote collaboration administration and server-local maintenance.

## Opening files

```sh
scribe add [--force] [--offline] <path|dir>...  # new files; directories recurse with ignore rules
scribe edit [--offline] <path>...               # tracked files you will modify
scribe delete [--force] [--offline] <path>...   # tracked files; removes the local file
scribe revert <path>...               # restore the recorded base; reverting an add keeps the file
```

`add --force` overrides ignore rules only: it never adds links, names
Windows cannot represent, anything under `.scribe/_state/` (reserved local
workspace state; the rest of `.scribe/` is ordinary repository content), or
files outside the workspace. `delete` refuses a locally modified file unless
`--force`. The removed file goes to `.scribe/_state/recovery/` whether or not
it was modified, and `revert` restores the file from its base. `revert` of an
edit moves the local content there too. Each path answers for
itself: a refused path is reported as skipped while the others are still
opened.

While a merge or other conflict resolution is active, `add`, `edit`, and
`delete` are refused: work opened then would be submitted inside the integration
commit and inherit its lineage. Finish it with `scribe merge --continue`, or
discard it with `scribe merge --abort`, and open the unrelated paths afterwards.
`scribe reconcile --scan` reports such files instead of opening them.

### Move files and folders

```sh
scribe move Assets/Characters Assets/Actors
scribe status
scribe submit -m "Move characters into Actors"
```

For one file, use `scribe move Assets/Old.uasset Assets/New.uasset`.
The destination is an exact new path. A folder carries its untracked and ignored
contents too; only tracked files are opened for submission. Both subtrees must
be fully in view. Source files may belong to earlier pending moves or be opened
for edit; other opened work must be submitted or reverted first. Local edits,
opened or not, travel with the file and submit with the move as one entry.
Case-only leaf and folder renames use the same explicit command. Existing
destinations, links, invalid names, and colliding source names are refused.

A rename done in the file manager or an editor shows up as a missing tracked
file plus an untracked one, and would submit as a delete and an add. Like
`p4 reconcile`, `scribe move --detect` pairs them afterwards:

```sh
scribe move --detect --dry-run     # list the pairs it can vouch for
scribe move --detect               # record each pair as a pending move
```

A pair is a missing tracked file, opened for edit or not, and an untracked file holding exactly its
recorded bytes: only untracked files of the same length are hashed, and only
a content id equal to the baseline's pairs. Identity is content, so a file
edited on the way is not paired, and two missing files with the same bytes
(or two untracked copies of one) are reported as unpaired rather than
guessed; record those with `move --after`. Recorded pairs are ordinary pending
moves, already applied on disk: they show in `status`, submit together, and
`move --abort --id <ID>` undoes one by restoring the original path. The app
offers the same under Submit ▸ Find Renames….

Engine editors and IDE refactors often change a file as they rename it: an
Unreal asset rename rewrites the package path stored inside the `.uasset`. No
content match can vouch for such a pair, so name it:

```sh
scribe move --after Content/Hero.uasset Content/Actors/Hero.uasset
```

`--after` records a rename that already happened. The source must be gone and
the destination must be a present, untracked regular file; its bytes may
differ. The source may be opened for edit. Submit records the move and the new
bytes as one history step, and `move --abort` puts the current bytes back at
the source path. `--after` takes one tracked file; a folder renamed outside
Scribe is recorded file by file.

Multiple moves can remain pending and be submitted together. Rename chains such
as `A -> B -> C` retain the original accepted history; submission records `A -> C`.
A folder can include files renamed by earlier pending moves.

`status` lists each move's ID. `move --abort --id <ID>` undoes a selected move;
without `--id`, abort selects an interrupted move first, otherwise the latest.
Independent moves can be undone in any order. If a later move depends on the
selected move, abort that later move first. Each undo restores the previous
location and pending state while retaining the latest local bytes.

If interrupted, `move --continue` resumes that move. Only one move can be
interrupted at a time; finish it before starting or undoing another. A vacated
source location remains reserved until submission or abort, so cycles and
source-path reuse require finishing the earlier move first. Moves refuse to
overwrite an obstruction. Original work in transit remains under
`.scribe/_state/staging/move-<operation>`; if that entry, the source and the
destination are all gone, `--continue` refuses and `--abort` retires the move
without restoring anything, and says so. Use this abort command rather than
reverting individual move endpoints. Updating source revisions requires finishing pending
moves first. If a move is rejected because its source is stale, abort pending
moves, update the original files, then reapply the moves.

Submit validates both endpoint locks and the source's exact revision. A source
lock held by this workspace is also acquired for the destination with a new
generation; the original remains locked for branches that still use that name.
Release both when finished. A folder move is limited to 50,000 tracked files
and 100,000 discovered filesystem entries. A workspace can have up to 1,024
pending moves with at most 32 MiB of combined journal data.

## Status and scanning

```sh
scribe status                # opened paths and pending workflows; no network
scribe status --scan         # also unopened tracked changes and untracked files
scribe status --verify       # scan and hash tracked contents
scribe status --remote       # add current branch head, main lock rules, and holders
scribe status --scan --remote # combine discovery and live collaboration state
scribe scan [--verify]       # same as status --scan
scribe ignore explain <path> # the deciding rule, or the excluded parent
scribe diff [<path>...]      # unified diff of opened text files against their baseline
scribe diff --revision 41 <path> # against an accepted change instead
```

`diff` is read-only and needs the server for the baseline bytes; it prints the
usual `---`/`+++` unified format for opened text files, `Binary files ...
differ` for files with NUL bytes, and `too large to diff` above the 2 MiB
review bound. Nothing is opened, locked, or written.

Status shows one row per canonical path. Each row combines opened intent and
actual local state with recorded move sources/destinations, offline reconciliation,
deferred sync, resolution choices/groups, and optional live lock information.
Moves also have an operation summary with IDs and dependencies for resuming or
aborting a specific move. Chained moves show the original accepted source and
final opened destination; intermediate names do not become phantom changes.

Unresolved conflicts are distinct from saved choices awaiting application.
The workflow summary reports planning, conflicts, applying, resolved, or aborting;
resolved means the local plan is applied, not submitted. Pending submit outcomes,
branch operations, branch switches, and sync journals are also visible, with
relevant next-command guidance: `Next: scribe submit ...` when files are opened
and nothing else is pending, `Next: scribe reconcile ...` only when an edit was
opened offline, and the pending workflow's `--continue` / `--abort` otherwise.
A trailing `Summary:` line counts paths by intent so a long listing still ends
with the totals. A file can be opened, modified, deferred, and
pending reconciliation at the same time; none of these facts hides the others.

Default status inspects opened and workflow paths only. It explicitly says that
unopened changes and untracked files were not scanned; an empty report never
claims the whole workspace is clean. `--scan` widens discovery. `--verify` hashes
contents to detect changes that size and modification time alone can miss.

A scan trusts an unchanged size and modification time only when the recorded
state is settled: the file's modification time must be older than the moment the
workspace recorded it by that filesystem's timestamp granularity. A modification
time with sub-millisecond digits cannot be reproduced by a later save, so it is
trusted immediately; a whole-second time (FAT, some network filesystems) is
trusted two seconds after it was recorded, and a sub-second time one second
after. Otherwise a save of the same length within that window would be invisible,
so those files are hashed during an ordinary scan until their contents change.
Ignore rules never hide tracked changes. Inspection failures are attached to
the affected path without suppressing other rows or treating failures as missing
files. A directory in place of a file, or a file blocking a parent directory, is
reported as `not_file`, including file/directory rename conflicts. Status never
changes files, opened intent, reconciliation state, or locks.

Remote information is **not checked** by default. Even a locally recorded lock
acquisition is not evidence of current ownership. `--remote` reads the current
branch head, accepted main `.scribe/config.toml`, and repository lock holders within
the workspace view. Each reported path then shows required/optional locking and
unlocked/this-workspace/other-workspace ownership. Lock-only paths have no local
content inspection; fetching lock information does not hash those assets.
These separate server reads are observations, not a transaction or a promise
that a later submit will succeed. The branch head and sync watermark are shown
as revision numbers, not a misleading count of changes behind.

If an explicit remote check fails, status still prints the full local report,
marks remote information **unavailable**, leaves lock ownership unknown, and
exits 1. It publishes no partial lock page. Local inspection or ignore-rule
problems also produce exit 1 with the report. Ordinary file changes, conflicts,
and pending workflows do not themselves produce a nonzero exit. JSON output
uses the same per-path model; see [the output reference](/docs/json-output.md#unified-status).

## Submitting

```sh
scribe submit -m "message"
# Or publish only selected opened files:
scribe submit -m "Fix movement" -- Source/Player.cpp Source/Movement.cpp
```

Use **submit** for the action and **change** for the accepted result, identified
by a repository-wide change number. Technical specifications also use **commit**
for the immutable history object and **commit envelope** for its durable record.

A submit publishes directly to the server—the equivalent of Git’s "commit and
push". Without paths it includes all opened changes; with paths it includes only
the selected exact opened files. Unselected work and locks remain intact. Paths
are relative to the current directory, must stay in the current workspace, and
can name deleted files. Directories and unopened files are refused. Selecting a
move endpoint includes the complete folder move and dependent rename chain.
Active merge, cherry-pick, and conflict resolution workflows require a full submit.

The client chunks and hashes each selected opened add and edit, uploads only chunks the
server lacks together with manifests and the change set, then persists the
candidate and operation ID before sending submit. It finalizes local baselines after the server's durable acknowledgement. A file
that changes while it is read or uploaded is refused.

- accepted: prints the change number, file count, chunks sent, bytes on the
  wire versus logical bytes, and server flush count;
- rejected (`base_mismatch`, `locked_by_other`, `path_collision`, …): no new
  history is accepted, and opened work is retained. For ordinary stale opened
  files run `scribe resolve --update`, choose or edit each resolution, and submit
  again. Pending moves must be finished or aborted before updating their source
  revisions. If the target advanced, a grouped merge or partial-view integration must be
  aborted and restarted;
- unknown submit outcome (reply lost): exit 3. The candidate is kept; the next `scribe
  submit` first asks the server what happened. If no outcome is recorded, it
  replays the same ID and exact intent, including merges. It never infers a
  rejection from absence. New command-line paths and message do not replace the
  pending candidate or start a second submission in that invocation.
  Immutable uploads finish before the candidate is
  persisted; interrupted uploads can safely be re-analyzed and repeated.

While a submit outcome is pending, resolve it with `scribe submit` before using
`sync`, `add`, `edit`, `delete`, or `revert`. This keeps the candidate's local
state intact until the server confirms its result. Status and scan remain available.

## Importing an existing tree

A first population of a repository from an existing project is usually larger
than one submit may be. Acceptance is a single durable transaction taken under
the repository's publication gate, so the server caps the change-set entries it
takes at once (`[limits].max_submit_entries`, 100,000 by default) and refuses a
larger submit with `limit_exceeded`. Use `import` for that first population:

```sh
scribe add .
scribe import -m "initial import" --dry-run   # states the plan, sends nothing
scribe import -m "initial import"
```

`import` submits the opened adds in path order as a sequence of changes, each
within the cap it reads from the server rather than one compiled into the
client. If the server refuses a batch anyway — a lower cap, or a gateway
reporting the default — the batch is halved and re-sent. Every change carries
the message verbatim; the plan and each accepted change are reported on stderr
as the import runs.

Import is resumable, and a million-file import will be interrupted. Each batch is
an ordinary submit: its operation id is persisted before transmission, and an
unknown outcome is resolved by asking the server rather than guessed. Just run
the same command again — it resolves the batch that was in flight, then continues
with the paths the server has not accepted. Re-running a finished import succeeds
and says so, so it is safe to repeat until it does.

`import` accepts only adds, and refuses an opened set containing an edit, a
delete, a keep, or a move destination, naming the path. Splitting is sound here
because every entry is an add against an empty repository, so each intermediate
change is a coherent prefix of the tree. It is not sound in general: the delete
half of a rename could land in one change and the add half in the next,
publishing a state in which the file exists nowhere, and rename pairs can be
arbitrarily large, so no batch size fixes it. Ordinary work — including a large
reorganization full of renames, which is exactly what the cap targets — stays
one atomic `scribe submit`. For the same reason there is no `submit --split`.

An administrator can also raise `max_submit_entries` for a one-off import, but
that blocks every other submit on the repository for the length of the
acceptance; `import` does not.

## Syncing and exporting

```sh
scribe sync                         # to head
scribe sync --revision <change>     # to a pinned change, including an older one
scribe export --revision <change> --to <empty dir> [--repo NAME] [--view PREFIX]...
```

Sync writes a journal before touching files, applies it in batches, reuses
unchanged chunks from the local copy and the cache, verifies every reconstructed
file's content id, and advances the watermark only when everything is applied.
An older target uses verified backward changes when the workspace has a verified
baseline; otherwise sync proves a full snapshot and reconciles against it. Every
local file about to be replaced is moved to `.scribe/_state/recovery/` first,
including files that equal their recorded base, and the report names each
location, or a count when more than ten were displaced. See [the recovery
area](/docs/client-usage.md#the-recovery-area) for listing and pruning.

Sync never replaces local work: a path whose server version differs from its
baseline is skipped and listed as deferred when it is opened, when its bytes are
not its baseline's (edited without `scribe edit`), or when an untracked local
file sits where the server now has one, and a server-side deletion of such a
file is deferred the same way. A deferred path stays deferred on every later
sync until you decide: `scribe revert <path>` takes the server's version and
moves your bytes to recovery, and an opened path can instead be reconciled and
submitted. Only a view change replaces rather than defers, since the selection
you chose is the decision.

A path whose baseline already is the server version has nothing to apply and is
completed as unchanged, opened or not: local edits to it, including ones made
after the submit that recorded that baseline, are left exactly as they are. An
accepted submit advances the synced marker itself when its change number is the
next one after the synced change (one sequence serves every branch, so nothing
else can have landed between them) and no journal, view change, mount, or
repository-configuration edit is involved; otherwise the next sync lists the
submitted files and completes them as unchanged.

Files another program holds open (Windows sharing violations) are reported and
retried on the next run, which resumes from the journal. An interrupted revert
retains its deferred update; rerun `revert` to finish it.

`export` writes a pinned tree into an empty directory with no workspace state.

### The recovery area

```sh
scribe recovery list                       # every retained entry: time, reason, original path, location
scribe recovery prune --older-than 7d      # remove retained entries older than a week
scribe recovery prune --all                # remove every retained entry
```

Sync, revert, delete and conflict resolution move each local file they would
replace or remove into `.scribe/_state/recovery/`, under an opaque name with an index
beside it that records the original path, the reason (`sync-update`,
`sync-remove`, `delete`, `revert`, `resolve`, `project`), the entry's operation
id and the time. Even files that equal their recorded base are retained:
an editor can save after a content check or through an open handle after the
file moves. Budget recovery space for the old versions displaced by updates
and view changes. Nothing is pruned automatically. Close editors using retained
files and review the recovery entries before pruning. `prune` needs `--older-than` (a
number with `s`, `m`, `h`, `d` or `w`) or `--all`, removes only entries Scribe
retained and can map back, and keeps intent archives such as
`submit-<operation>.pending` and any entry whose index it cannot read, naming
each one it kept.

## Downloading one historical file

```sh
scribe get Assets/Hero.uasset --revision 123 --to ./Hero-old.uasset
scribe get Assets/Hero.uasset --revision 150 --branch art --to ./Hero-art.uasset
# Outside a workspace, select the repository explicitly:
scribe --server tcp://localhost:7447 get Assets/Hero.uasset --repo game --revision 123 --to ./Hero-old.uasset
```

`get` downloads exactly one file from the selected branch's snapshot at the given
repository change number. The source path is always **repository-relative**, even
when invoked from a workspace subdirectory; use the spelling that existed at
that revision. For a renamed or deleted file, consult `log` for its historical
path and an earlier revision. A missing path or directory is an error.

Without `--repo`, the repository and default branch come from the workspace.
With `--repo`, the default branch is main. `--branch` accepts a name or ID and
selects another branch without switching the workspace. Revisions outside the
selected branch's lifetime are refused. The reported `file_change` is the last
change to that file at the selected snapshot and can precede `revision`.
Workspace views do not restrict this explicit download; normal repository read
authorization still applies.

`--to` is a new local filename, relative to the current directory or absolute.
Its parent directory must already exist. Existing files, directories, and
symlinks are refused, including destinations created by another process during
the transfer. Destinations inside `.scribe/_state` are refused. There is no overwrite
or force option.

The client uses an exact indexed lookup and fetches only this file's manifest
and chunks. It verifies chunk identities and the complete reconstructed file,
preserves the stored executable mode, flushes it, and publishes it with an
exclusive rename. It does not change the workspace branch, baselines, opened
records, sync watermark, locks, or object cache. A download placed inside the
working tree is an ordinary local file; use `add` if you intend to track it.

Reconstruction uses a unique `.scribe-get-<id>` directory beside the destination.
Normal failures clean up staging and leave the final filename absent. A killed
process may leave its staging directory; after confirming that process is no
longer running, it can be removed and the command retried. If interruption
happens after publication, the destination is already the verified file and a
retry refuses to replace it. A directory-flush failure reports that the verified
file has been published. Downloads require server connectivity.

## Branches

Use the [branch workflow](/docs/branches.md#commands) to create a development branch,
switch the workspace, and publish changes. Branches retain stable identities;
locks remain repository-wide. The [requirements and limits](/docs/branches.md#requirements-and-limits)
explain clean-workspace prerequisites and revision selection.

## Merging and resolving

A merge is prepared locally, resolved, then submitted as shared history. Follow
[the branch and conflict guide](/docs/branches.md) for a complete workflow, including
stale submissions, side selection, continuation, abort, and external tools.
Planning checks locks before writing any file: a path another workspace holds
refuses the plan, and a path the active policy requires a lock for is acquired
for this workspace first. `merge --abort` releases exactly those acquisitions.
The [merge](/docs/reference/cli/scribe.md#merge) and [resolve](/docs/reference/cli/scribe.md#resolve)
references list all options.

## History, info, locks

```sh
scribe log [path] [--limit N] [--before CHANGE] [--after-path HISTORICAL_PATH]
scribe log <path> --first-parent # omit merge-source revisions; retain branch origins
scribe log <path> --all-branches # discover related submitted work on every branch
scribe describe <change> -m "…" # correct a change's description after acceptance
scribe describe <change> --history # every description ever recorded for it
scribe info <path>              # base, local state, head revision, lock holder
scribe lock acquire <path>...   # exclusive across branches, held until explicitly released
scribe lock release [--force] <path>...
scribe lock list [--prefix DIR]
```

File logs follow recorded moves, branch origins, and merge-source
ancestry by default, newest change first, with shared ancestors shown once.
Sources stop at the revision actually merged; later unmerged source edits
do not appear. `--before CHANGE` alone is an exclusive pagination bound on returned
revisions, including source revisions reached through newer merges.

Each log entry identifies the branch where it was accepted. JSON includes
`branch` (stable ID), `branch_name` (current name), and `merge_parent` (source
branch ID and revision, or null). A branch rename changes the displayed name,
not the revision's identity. Repository logs without a path still follow only
first parents. `info` reports the target branch's latest path state, excluding
merge-source revisions that did not change the target.

Merge ancestry records which history participated, not which source bytes
were selected during conflict resolution. An ancestry-only merge can expose
source revisions without adding a target revision to that path's log. File
moves carry explicit predecessor records; logs retain earlier names and revisions.
Use `--all-branches` to search related work beyond this ancestry.

A successful submit releases your locks on submitted files atomically with the
accepted change. Use `scribe submit -m "Description" --keep-locks` to retain them.
Locks on files outside the submission remain held. Rejections keep locks; after
an unknown outcome, recover the saved submit before deciding what happened.
A retry preserves the original keep-locks choice.

A submit touching a path locked by another workspace is rejected. `lock release`
releases only this workspace's acquisition; `--force` releases someone
else's, and on an authenticated server needs the maintainer role.

Locking a path that does not exist at head is normal for new files, deletions,
and move destinations, but the number of such locks a repository holds at once
is bounded (4,096 by default). `limit_exceeded` on `lock`, `edit`, or
`reconcile` means those pending locks must be submitted or released first;
`scribe lock list` lists them.

File-history rows expose `path` (the actual historical name) and optional
`moved_from: {path, change}`. To continue a file log, pass both the last row's
change to `--before` and its path to `--after-path`; this retains entries tied at
the same changeset. `--first-parent` still follows explicit local moves.

Merges follow recorded file relationships across renames and folder moves.
An edit on one branch is matched to the renamed file on the other branch; the
name and contents are merged separately. See [rename-aware merging](/docs/client-usage.md#rename-aware-merging).

### Correcting a description

A submit message is part of the accepted change and is never rewritten. When
it needs fixing — a typo, the wrong ticket number, a missing reviewer —
`scribe describe <change> -m "…"` records a corrected description beside it.
`log` then shows the description in place of the message, with who revised it
and when; `--json` carries both (`message` and `note`). Every description ever
recorded stays on the server: `scribe describe <change> --history` lists them
oldest first, and the submitted message remains in the commit and in `log`
for clients that predate descriptions.

You can describe your own changes; other people's need a maintainer. A
change is yours when your account submitted it: the server decides from the
owner it recorded for the submit operation, not from the author text, so a
change accepted before the server used authentication has no owner and needs a
maintainer. The server attributes each revision to the signed-in principal,
whatever author the workspace names. Two people describing the same change at once cannot
overwrite each other: the second is refused with `note_revision_stale` and
sees the current description before deciding again (`--expect` pins the
revision explicitly for scripts).

### Rename-aware merging

```sh
scribe merge art
scribe resolve
# Choosing any displayed member chooses the complete rename group:
scribe resolve --theirs Characters/Hero.uasset
scribe submit -m "Merge art changes"
```

The planner uses both branches' changed-path indexes and the recorded revision
lineage to match files to the pinned merge base. It follows chained renames and
handles each tracked file in a folder move. It does not infer identity from
matching filenames or contents, or from filesystem moves made outside Scribe.
An earlier merge that kept target bytes still supplies usable ancestry.

| Changes since the base | Result |
| --- | --- |
| One side renames; the other edits | Keep the renamed destination and merge the contents there. |
| Both sides choose the same new name | Keep that name and merge contents normally. |
| Both choose different names | A `rename/rename` group requires an explicit choice. |
| One renames; the other deletes | A `rename/delete` group requires an explicit choice. |
| A destination is occupied, collides by case/normalization, or conflicts with a file/directory | A destination-collision group requires an explicit layout choice. |

Disjoint text edits inside the view can merge automatically under the existing
size and work limits. Conflicting text and binary edits require resolution.
Choosing `--ours` for an included path whose source and target differ retains
the target state and records a keep decision, shown in `scribe status` as opened
for `keep`. Excluded automatic decisions remain in the resolution journal without
opening a local file. The submission carries these decisions so the server can
verify coverage of the whole source delta. Excluded conflicts require explicit
inclusion; see [partial-view integration](/docs/partial-view-merging.md).
When the name is agreed, `--ours` and `--theirs` choose **contents at that
destination**, retaining the rename. When names conflict, these choices select the corresponding layout
for the entire group, including colliding tracked files. Review the displayed
members before choosing a side. Independently reusing a vacated filename never
silently redirects the original file's edits into the new file.

`resolve` lists the group members, original/target/source names, and any path
conflict. Selecting one member with `--ours`, `--theirs`, or `--working` selects
the whole group. The choice is saved atomically before application; interrupted
application resumes with `scribe merge --continue`. `--all` selects every group.

For a content conflict with an agreed renamed destination, write the resolved
file at that destination before using `--working`. Scribe removes the obsolete
source as part of the recorded rename; a missing destination is refused. For a
name or destination conflict, arrange the desired files among the displayed
group paths first, then use `--working` to capture that layout. Immutable inputs
remain available through `scribe resolve --export ./inputs`.

Every replaced file is retained in `.scribe/_state/recovery`. Abort restores the
pre-merge target layout and retains work written after a choice. Accepted merges
record target-side move predecessors when a file is added at a new path and its
old target path is deleted. Existing lock and submit checks still apply.

If the target advances and a grouped merge is rejected, abort and restart the
merge so names and destinations are planned against the new target. Incremental
`resolve --update` is refused for grouped merges; keep/reapply recovered manual
edits as needed. Ambiguous lineage with multiple base-file ancestors is refused
before applying the plan; abort and choose an appropriate earlier explicit
`--base` to plan again.

Planning remains bounded to 100,000 touched paths, 32 MiB of path names, and
128 MiB of resolution records. Each atomic rename/collision group has at most
1,024 paths. Origin-query batches have a shared metadata work budget and return
an error rather than guessing when it is exceeded. These limits are checked
before applying the plan. No complete repository tree or unrelated working-file
contents are scanned for rename detection.

### Cross-branch file history

```sh
scribe log Assets/Hero.uasset --all-branches --limit 20
# Continue using the snapshot, change, and historical path from the last row:
scribe log Assets/Hero.uasset --all-branches --at 150 --before 123 --after-path Assets/Hero.uasset --limit 20
```

The selected file is the latest accepted revision of that path on the workspace's
branch at the query snapshot. A deletion can select the last file incarnation;
a later ordinary add at the same path starts a separate incarnation. To inspect
the earlier incarnation, select a snapshot before the replacement with `--at`.
A path absent from that branch's stored history returns no rows.

The server follows recorded predecessor relationships in both directions:
edits, deletions, explicit moves, and per-file merge entries. Branch snapshots
share their original revisions, so unmerged sibling and nested branch edits are
found without switching workspaces. Both endpoints of a move can appear at the
same change. Independently added files remain separate even when their names or
bytes match. An ancestry-only merge does not itself create a per-file identity
link between independently added files.

Each row shows the accepting branch, historical path, and one of:

- **in ancestry**: the accepting commit is reachable from the selected branch
  at the snapshot, including recorded merge parents;
- **related; outside ancestry**: a related revision on another line of development
  whose accepting commit is not reachable there.

These labels describe commit ancestry, not whether a merge selected that
revision's contents. In particular, a merge that kept the target bytes still
records the source ancestry. Unsubmitted and offline local edits are not server
history and do not appear.

The first request pins the current repository change number and prints it as
`snapshot`; JSON calls this `history_snapshot`. Reuse it as `--at` on every page,
along with the last row's `--before` and `--after-path`, to keep membership and
file selection stable while teammates submit or rename files. Branch display
names remain current. `--all-branches` requires a path and conflicts with
`--first-parent`; `--at` requires `--all-branches`.

Queries use an indexed revision graph and sparse merge metadata; they do not
scan repository trees or download file contents. Work is bounded to 100,000
metadata visits and a 32 MiB lineage path budget. Exceeding either limit returns
an explicit error, never partial history. Pagination bounds the returned rows;
each request still discovers the connected lineage at the pinned snapshot.
The operation requires repository read access and does not modify the workspace.

### Mandatory lock rules

Put lock rules in the repository's `.scribe/config.toml`, alongside any
workspace profiles:

```toml
version = 1

[locks]
rules = [
  '*.uasset',
  '*.umap',
  '*.blend',
  '*.psd',
  'Art/**',
  '!Art/Generated/**',
]
```

Rules apply to any file type. Matching ignores case and Unicode normalization,
under the same fold as path collisions (`*.psd` also locks `Hero.PSD`), is
root-anchored, and uses Git-style globs and escaping. TOML comments belong outside
rule strings. A pattern with no slash matches a filename at any depth; a leading
slash anchors at the root. Last matching rule wins. `!` removes the mandatory
requirement; unmatched paths permit ordinary editing. A trailing `/` selects the
subtree. Unlike ignore traversal, a directory rule cannot hide a later exception.
Only the root config participates; there are no nested or local lock overrides.

The accepted **locks section on main** governs **every branch**. Local edits,
old sync revisions, and branch-local proposals cannot override it. Changing the
ordered lock-rule values on main requires Maintainer permission, including through
merges, section removal, file deletion, or moves. Profile-only changes and TOML
formatting changes retaining the same rules remain Writer operations. The old
policy governs its own updating changeset; after acceptance, later submissions
use the new policy. Removing rules never releases existing locks. Without a locks
section, mandatory locking is disabled; manual locks remain enforced.

The entire configuration is validated on every branch: UTF-8 TOML, at most 64 KiB,
version 1, known fields only, and at most 1,024 lock patterns. Rule diagnostics use
actual TOML source lines. See [repository configuration](/docs/repository-config.md)
for the complete schema, limits, and permissions.

```sh
scribe lock rules                  # active server policy, regardless of workspace branch
scribe lock rules Art/Hero.psd     # requirement and deciding rule
scribe edit Art/Hero.psd           # acquire a required lock, then open for editing
scribe status --remote             # current holders and requirements alongside local status
scribe info Art/Hero.psd           # includes requirement, rule, and active policy revision
scribe lock release Art/Hero.psd
```

By default, every new `edit` contacts the server for the current policy, including edits to
nonmatching files. If the server cannot be reached, it fails before changing
opened state or read-only permissions. Already-opened work remains editable
offline; submit needs the server. Ordinary `status` and `scan` remain local;
`status --remote` explicitly adds network queries. Repeating `edit` on a file
that is already opened reports it as `skipped PATH: already opened` because the
opened record is unchanged, but it still checks the policy and reacquires any
required lock first, so it is the way to recover a lost or revoked lock. An
online `edit` also warns on stderr when another workspace holds a lock on a
file it just opened, since submit will refuse that file until the lock is
released. Use explicit `edit --offline` to open local work without connecting;
see below.

For a batch edit, required locks are acquired before any opened-state or
permission changes. Successfully acquired locks remain held if a later
acquisition fails; inspect with `lock list`, retry, or explicitly unlock. A lost
lock reply leaves files unopened; retrying acquisition returns the same lock
while this workspace still holds it. Locks survive disconnects. Successful submissions release submitted locks by default.
There is no automatic expiry or silent forced release.

Submit rechecks the current policy and actual ownership in the durable metadata
transaction. A newly introduced rule or revoked lock can therefore block an
already-opened edit. Acquire the lock and submit again; local work is retained.
Mandatory ownership also covers adds, deletes, and move endpoints. Use `lock`
explicitly before submitting matching new files or deletions. A move can acquire
its destination atomically from its owned source lock; a destination held by
another workspace always rejects the move. With `--keep-locks`, the source lock is retained and the destination is acquired; otherwise both submitted endpoints are released.
A merge or cherry-pick acquires the locks its own result needs while planning,
before it rewrites any working file, and refuses to plan over a path another
workspace holds; keeping the target's version needs no lock. `merge --abort`
releases what that merge acquired, and an accepted merge releases submitted locks unless `submit --keep-locks` was selected. See [branches and conflict resolution](/docs/branches.md).
Rules describe cooperative editing requirements; they cannot prevent arbitrary
filesystem writes, and server enforcement never trusts a client's local policy.

## Offline work and reconciliation

For a step-by-step exercise, follow [Work offline](/docs/tutorials/work-offline.md).

In an existing workspace, explicitly open a tracked file without connectivity:

```sh
scribe edit --offline Art/Hero.psd
# Edit in your usual tools.
scribe status
# When connected again:
scribe reconcile
scribe submit -m "Update hero"
```

`edit --offline` makes no server connection. It durably records local intent
before making a read-only file writable, and marks it **pending reconciliation**.
It does not acquire or promise a lock, and does not consult a local or cached
`.scribe/config.toml` as authority. Ordinary online `edit` never silently falls back to
offline mode. Repeating it on an offline-opened file can acquire a lock, but
leaves the marker until reconciliation checks revisions too.

For planned offline work, run ordinary `edit` on required assets before
disconnecting: acquired locks survive disconnects. Use `submit --keep-locks` if
you need to retain submitted locks too. Already-opened files remain writable offline. Administrators can explicitly revoke locks, so
submit always rechecks ownership. `add`, `delete`, and `move` record intent
locally first; their opened records start pending reconciliation. `add` and
`delete` then make the reconciliation round trip at once for the paths they just
opened, so a file the lock policy covers is locked as `edit` would lock it, and
report what it found. An unreachable server skips that check and leaves the
records pending; `--offline` skips it without trying, so offline work never
waits on a server that is down or behind a lost VPN. The desktop apps do the
same, recording without the server when they know they are offline. Local conflict resolutions
that retain opened work set the same marker. Reconciliation is a preparation
step, not a mandatory extra round trip before every submit: submit can accept
valid work directly and never trusts this local marker.

`reconcile` checks all opened paths against one pinned head of the workspace's
branch, reads the current accepted **main** lock policy, and acquires the required
locks, including new paths, deletions, and both move endpoints. It also reports
other-workspace manual locks on optional paths. Exact state reads are batched;
there is no full server-tree download. No file bytes, baselines, sync watermarks,
or recovery contents change. Ready paths clear their marker; blocked paths keep
it. Readiness describes the checks at the reported revisions, and is not a
promise that a later submit will succeed: policy, revisions, or ownership may
change in between.

A failed lock acquisition does not stop checks for unrelated files. Locks
successfully acquired earlier remain held, including locks on paths that need
revision resolution. A transport or storage failure stops reconciliation;
markers remain pending if final local recording has not completed. Retry the
same command: reacquiring a lock still owned by this workspace is idempotent,
including after a lost reply. Use `scribe lock list` and explicit `scribe lock release`
to release acquisitions you no longer need. A pending submit outcome must be
resolved with `scribe submit` before offline edit or reconciliation can proceed.

For a stale revision, use the existing resolution workflow, review the result,
and reconcile again:

```sh
scribe resolve --update
scribe resolve                         # inspect remaining conflicts
# Edit an unresolved file, then record that choice:
scribe resolve --working Art/Hero.psd
scribe reconcile
scribe submit -m "Resolve offline work"
```

Binary conflicts may need an external editor or an explicit ours/theirs choice.
If a pending move's source advanced, abort the move first (dependent moves in
reverse order), resolve the source, and repeat the move; automatic updating of
pending move sources is not implemented. Reconciliation itself never chooses
versions, steals locks, or discards local content.

If stale work belongs to a grouped merge, abort and restart that merge instead
of using `resolve --update`; retain and reapply recovered manual edits. See
[rename-aware merging](/docs/client-usage.md#rename-aware-merging).

To collect edits made by tools outside Scribe:

```sh
scribe scan --verify                 # read-only discovery; works offline
scribe reconcile --scan              # online: discover and open tracked edits, then check
```

`--scan` hashes tracked contents using the existing verifying scan, so it can be
expensive on a large workspace. It opens discovered modified tracked files and
reports missing tracked files and untracked files. It does **not** infer deletes,
adds, or renames: use `delete`, `add`, and explicit `move` for those intentions.
Ignore rules still affect only untracked discovery. An opened edit/add with a
missing local file or an opened deletion with a reappeared local file is reported
as blocked, preserving the recorded intention and any local content.

Reconciliation exits 0 when all **opened** paths pass its checks, or 1 with a
per-path report when some need attention. Discovery-only missing/untracked paths
are listed separately and do not change that exit status. With `--json`, both
outcomes emit one report object; connection and other execution failures use the
normal client error output.

## Ignore files

`.scribeignore` files are versioned and nest; `.scribe/_state/ignore` is local; a
`--global-ignore` file has the lowest precedence. Git-style syntax; deeper
files win; the last match in a file wins; an excluded directory is not
entered. A malformed rule stops discovery rather than being skipped. Details
in [ignore-and-paths.md](/docs/ignore-and-paths.md).

## Typical day

```sh
scribe workspace create game ~/game --view Assets --view Source --author ann
cd ~/game && scribe sync
scribe lock acquire Assets/Hero.uasset && scribe edit Assets/Hero.uasset
# ... work ...
scribe status
scribe diff
scribe submit -m "hero rig update"
scribe lock release Assets/Hero.uasset
scribe sync
```

When the active policy already requires a lock for the asset, `scribe edit`
acquires it automatically and the explicit `lock` step is unnecessary.

## Compression

Repository storage and connection compression are independent. Both default
to `none`. Choose the permanent repository storage policy at creation:

```sh
scribe repo create game --storage-compression lz4
scribe --transfer-compression lz4 workspace create game ./game
```

The workspace creation command saves that transfer preference. Change it
later from inside the workspace:

```sh
scribe workspace config --compression none
scribe workspace config --compression lz4
```

`scribe workspace info` reports the saved preference. A global
`--transfer-compression none|lz4` overrides it for one command, for example
`scribe --transfer-compression none sync`; it does not change the saved value.
`scribe repo info game` reports the repository's immutable storage policy.
There is no API or CLI to change that policy after creation.

LZ4 only compresses object data, leaving control messages unchanged. Objects
that do not shrink remain raw on disk; transfers similarly retain raw blocks
when compression does not help. Compression does not change chunk IDs,
manifest IDs, deduplication, or accepted history. Working-copy files remain
ordinary uncompressed files. The workspace object cache uses raw storage.

Submit and sync JSON include `protocol_bytes_sent` and `protocol_bytes_received`
for that connection (handshake, metadata frames, and encoded object data; TLS
and TCP overhead excluded). Existing `bytes_sent`/`bytes_fetched` workload
counters count original object bytes and should not be used to measure wire
compression. `node tools/scenario.js` compares all four storage/transfer settings
on identical repetitive and random inputs, reporting submit/sync wall times,
logical throughput, protocol byte counts, and physical object-file sizes.

## Recover an interrupted submit after server restore

Run `scribe submit` to resolve the pending operation. If its result is still
recorded, the client applies that result. If the repository has a new incarnation
and no result is recorded, the client saves the exact old candidate under
`.scribe/_state/recovery/submit-<operation>.pending`, clears the blocking pending row,
and reports the archive path. Your current files and opened actions remain.

Then run `scribe sync`. Opened files are deferred; old baselines cannot be
submitted merely because change numbers happen to match restored history.
For edits, run `scribe resolve --update`, make an explicit choice (for example,
`scribe resolve --ours PATH`), and submit. Revert is also available and retains
displaced bytes in recovery. Abort pending moves before updating their source
revisions. An opened add that remains absent on the restored server can be
submitted normally after sync.

Without an incarnation change, a missing outcome remains unknown and the client
retries the same intent. It never clears a pending operation merely because its
outcome or an object is unavailable.

## Repository mounts

Use `.scribe/config.toml` to pin child repositories into virtual directories.
`scribe workspace mounts` lists declarations, and `scribe sync` materializes
selected children under their own permissions. See [repository mounts](/docs/repository-mounts.md)
for the config format, pin updates, partial failures, and local-work protection.

## Project orchestration

Ordinary `edit` and `submit` still handle files in one repository, including
parent files beside mounts. The new `project` group coordinates the parent and
selected mounted workspaces. Follow the [practical game-and-engine
walkthrough](/docs/tutorials/work-with-mounts.md) to choose between these workflows.

Use `scribe project status --scan --remote` to inspect selected components,
`scribe project sync` to materialize the pinned composition, and
`scribe project update NAME [--revision N]` to stage an explicit parent pin.
`scribe project publish -m MESSAGE` prepares opened changes, publishes children
first, and commits the parent with their confirmed revisions. Recover from the
root with `scribe project publish --continue` or `--abort`. See
[project workflows](/docs/project-workflows.md) for selection, recovery, and local-work
protections. No cross-repository atomicity is implied.

## Cherry-pick

Use `scribe cherry-pick <CHANGE> --from <BRANCH>` to stage one ordinary source
change, then review and submit. `--continue` resumes a saved pick; `--abort`
preserves displaced edits in recovery. See [cherry-pick workflows](/docs/cherry-pick.md)
for backports, conflicts, history and web/SDK examples.

## Integrate without downloading excluded assets

`merge` and `cherry-pick` work with partial workspace views. Both still integrate
the complete selected history; clean excluded paths reuse accepted content by
identity. Conflicts outside the view require explicit inclusion before acceptance.
See the [worked partial-view example](/docs/partial-view-merging.md).

## Transfer concurrency

Larger uploads and downloads automatically use up to four session-bound data
connections. Small transfers stay serial. Use `--transfer-jobs N` or
`SCRIBE_TRANSFER_JOBS` to set a limit from 1 to 8; `1` forces serial operation.
See [parallel transfers](/docs/parallel-transfers.md) for configuration and bounds.
