jx is more than a thin wrapper around the current jj checkout. It also keeps
an index of configured code roots so commands can find, name, and operate on
multiple primary repository clones consistently.
The layout model is deliberately small: every repository has a normalized identity, every identity maps to one visible primary checkout path, and managed workspaces live under a hidden sibling tree derived from that same identity.
Layout starts by normalizing repository inputs into four fields:
source- a configured source name such asgithubhost- the Git host such asgithub.comowner- the GitHub owner or organizationrepo- the repository name
Without configuration, jx has one built-in source:
[layout]
default_source = "github"
default_root = "~/src"
workspace_dir = ".work"
[layout.default]
path = "{host}/{owner}/{repo}"
[[layout.sources]]
name = "github"
provider = "github"
host = "github.com"
clone_url = "ssh"That means example-owner/example-repo resolves to the identity
github:example-owner/example-repo, clones from
git@github.com:example-owner/example-repo.git, and by default lives at:
~/src/github.com/example-owner/example-repo
Path templates may use {source}, {host}, {owner}, and {repo}. They must
render to relative paths without . or .. components. Layout roots must be
absolute or start with ~/.
For each repository identity, jx derives two related path families:
primary checkout: <root>/<path>
managed workspace: <root>/<workspace_dir>/<path>/<workspace-name>
With the default layout, a fix workspace for example-owner/example-repo is:
~/src/.work/github.com/example-owner/example-repo/fix
workspace_dir and workspace names are single path segments. Workspace names may
contain letters, numbers, _, and -.
Rules override the default root and/or path for matching identities. They match a
single source and at least one of owner or repo. Rules compose in config
order; later matching rules override the root or path chosen by earlier matches.
[layout]
default_root = "~/src"
workspace_dir = ".work"
[layout.default]
path = "{host}/{owner}/{repo}"
[[layout.rules]]
source = "github"
owner = "example-org"
root = "~/work"
path = "{repo}"
[[layout.rules]]
source = "github"
owner = "example-org"
repo = "special-repo"
path = "special/{repo}"This keeps most example-org repos under ~/work/<repo>, while
example-org/special-repo lives under ~/work/special/special-repo.
Global and project-targeted commands discover layout repositories by scanning the
configured layout roots for .jj workspaces. A discovered path is kept only when
it can be mapped back to either the primary checkout path or a managed workspace
path for one normalized identity.
jx assigns stable keys to discovered locations:
repowhen the repo name is uniqueowner/repowhen multiple owners have the same repo namesource:owner/repowhen evenowner/repois ambiguousrepo@workspacefor managed workspaces
Primary repository commands use only keys without @. Managed workspace keys are
for navigation and workspace management.
jx cloneresolves repository shorthands through layout sources and places the primary checkout at the configured destination, unless an explicit destination is provided. From a configured layout prefix, a single repo name can infer the missing source and owner.jx worklists, completes, resolves, adds, and removes locations in the configured layout.jx work addcreates managed workspaces under the hidden workspace tree, can prefix task workspaces with--task-id, andjx work removerefuses paths outside that managed tree.jx remote-statususes the current repository by default, can target one primary repository key, and can scan all configured primary repositories.--reporemains a glob filter for global scans.jx openuses the current repository by default, can target one primary repository key, and can use--repoglobs to open matching GitHub repository pages or matching pull-request searches.jx fetchuses the current repository by default, can target one primary repository key, and can scan every safe primary repository with--all.jx syncsyncs tracked bookmarks for the current repository by default and applies repository policy such as trunk advancement. From an uninitialized layout path, it can prompt to initialize the local jj/Git repository before continuing bootstrap. Pass a jj revision or bookmark to sync one bookmarked target instead, usejx sync --repoto force repository mode explicitly, or usejx sync --allto conservatively sync every eligible primary repository.jx stack publishuses an explicit--task-idwhen present; otherwise it can read the task id stored in workspace-local metadata created byjx work add --task-id.jx shell init bashexposes layout keys to shell completion. Navigation completion prefers current-repository layout workspace aliases,trunk/rootaliases, and same-repository layout keys before other global work locations; project argument completion includes only primary repositories. The navigation command accepts explicit absolute and dot-relative paths, and can also resolve unique key fragments plus slash-separated directory fragments under the selected location. In zoxide-prefer mode, zoxide matches win except for thedefault,trunk, androotjj aliases. An optional tab companion uses the same resolution and opens zellij tabs when available.
Task workspaces keep the task id visible in navigation while storing the task association as workspace-local metadata.
jx work add fix --task-id ABC-123This creates a managed workspace whose directory and jj workspace name are both:
ABC-123-fix
It also writes:
<workspace-root>/.jx/.gitignore
<workspace-root>/.jx/workspace.toml
The .gitignore file ignores the whole .jx metadata directory, and
workspace.toml contains:
task_id = "ABC-123"The visible workspace name makes completion entries such as repo@ABC-123-fix
scannable. The metadata file remains the source of truth for jx stack publish,
so the workspace name is not parsed for task information.
The layout index lets jx run maintenance commands over primary checkouts from
any directory. These global modes use only primary repository keys; managed
workspace keys with @ remain navigation/workspace targets and are not scanned.
jx fetch --all is intentionally broad but local-work safe. For each configured
primary checkout, it discovers fixed-origin repository context and fetches only
when the current workspace is a clean empty child of origin trunk. Repositories
that are not discoverable or are not safe for automatic fetch are skipped;
repositories that fail after selection are rendered as error rows.
jx sync --all is narrower because it can push. It does not initialize missing
repositories, create GitHub repositories, or prompt. A repository is eligible
only when all of these gates pass:
- The primary checkout is discoverable and already has a GitHub
origin. - The GitHub token can push to
origin. - GitHub
originis not ahead of the local cached trunk, and the origin branch is not diverged.
Eligible repositories run the normal repository sync steps: fetch origin,
optionally advance the local trunk bookmark according to repo policy, then push
tracked bookmark state whose push ranges have no conflicted commits. Conflicted
bookmarks are skipped and reported separately. Local jj work does not by itself
block sync --all; if GitHub origin has not moved ahead of the cached trunk,
pushing tracked bookmark state is still safe. Repos that do not fetch, advance
trunk, or push anything are grouped as up to date. Other skips are grouped by
reason so read-only third-party checkouts, pull-needed repos, and setup issues
remain visible without being treated as failures.
Commands with no project argument operate on the current working directory and
walk up to the enclosing .jj workspace. Project arguments resolve through the
global layout index first and then run the same command as if the process had
started in that repository's primary checkout.
This split lets jx stay predictable inside a workspace while still supporting
fast cross-repository workflows from anywhere with access to the configured
layout roots.