JoyJoy Docs

Features

Detailed feature list with CLI examples.

Available Beta

Item Types

Seven item types cover every kind of work:

TypePurpose
epicLarge initiative grouping multiple items
storyUser-facing functionality
taskTechnical work, not directly visible to users
bugSomething is broken
reworkRefactoring or improvement of existing code
decisionArchitecture or product decision to document
ideaNot yet refined - capture it before it escapes
joy add epic "Recipe Management"
joy add story "Add a recipe" --parent CB-0001 --priority high
joy add bug "Login fails on empty password" --priority critical
joy add decision "Use SQLite for local storage"

Status Workflow

Items move through a defined lifecycle:

new -> open -> in-progress -> review -> closed
         \                      |
          +---> deferred <------+
joy status CB-0002 open
joy start CB-0002                # Shortcut: set to in-progress
joy submit CB-0002               # Shortcut: set to review
joy close CB-0002                # Shortcut: set to closed
joy reopen CB-0002               # Reopen a closed or deferred item

Dependencies

Define what must be done before something else can start:

joy deps CB-0002 --add CB-0003   # CB-0002 depends on CB-0003
joy deps CB-0002                 # List dependencies
joy deps CB-0002 --tree          # Show full dependency tree
joy ls --blocked                 # Show items with unfinished dependencies

Joy detects circular dependencies and refuses to create them.

Milestones

Plan releases and track progress toward delivery targets:

joy milestone add "MVP" --date 2026-04-01
joy milestone link CB-0002 CB-MS-01
joy milestone show CB-MS-01      # Progress, risks, blocked items
joy roadmap                      # Full roadmap tree view

Children inherit their parent's milestone automatically.

Releases

Three explicit steps, so project-specific work (lockfile refresh, package registry uploads) can sit between them:

joy release bump patch           # Step 1: patch version strings in configured files
joy release record patch         # Step 2: record + commit + tag (local only)
joy release publish              # Step 3: push + forge release (auto-detects forge from git remotes)
joy release show                 # Preview the next release
joy release ls                   # List all releases

Event Log

Every command produces a structured, timestamped event committed to Git:

joy log                          # Last 20 events
joy log --since 7d               # Last 7 days
joy log --item CB-0005           # Events for a specific item

Authentication and Onboarding

Wrapped-seed Ed25519 identity (ADR-039). Each member has a randomly generated 32-byte seed encrypted twice in project.yaml: once under a passphrase-derived KEK, once under a recovery-key-derived KEK. Either wrap unlocks the same seed, so passphrase rotation preserves the keypair (and every Crypt zone wrap that depends on it). The recovery key is shown once at joy auth init and stored externally by the user; Joy never persists the plaintext.

Human members onboard via a one-time password shared out-of-band, then pick their own passphrase on their own machine. Every member entry is cryptographically attested by the admin who added it, so manual edits are rejected at the next auth.

joy auth init                            # Solo setup: pick a passphrase, save the recovery key
joy auth                                 # Re-authenticate (24h session)
joy project member add pete@team.org     # Prints a one-time password to share
joy auth --otp AB7X-K3M2-PQ9Z            # New member redeems OTP, sets passphrase
joy auth passphrase                      # Self-service passphrase change (keypair preserved)
joy auth recover --recovery-key          # Reset passphrase via recovery key (no re-onboarding)
joy auth recover --regenerate-key        # Rotate the recovery key from a live session

Selective End-to-End Encryption (Crypt)

Mark items, files, or whole directories as confidential with joy crypt add. They become AES-256-GCM blobs in the working directory, in Git's index, in every commit, in every clone, and on the forge - end-to-end ciphertext (ADR-040). No Git filters, no .gitattributes rules, no .git/config wiring; Git is pure transport.

Default zone for the common single-team case; named zones via --zone for multi-tenant setups; whole-project mode via --all. Cross-member grants use X25519 ECDH against the recipient's verify_key, so adding a teammate to a zone is a one-liner.

joy crypt add JOY-0123                   # encrypt one item now
joy crypt add data/customer-x/ --zone customer-x
joy crypt grant alice@team.com           # X25519 wrap of the zone key for Alice
joy crypt ls                             # paths, items, members for the addressed zone
joy crypt zone ls                        # per-zone summary

Free files keep plaintext off the local filesystem by default:

joy crypt read   data/customer-x/notes.txt | less
echo "new" | joy crypt write data/customer-x/notes.txt
joy crypt edit   data/customer-x/notes.txt   # $EDITOR on a temp; re-encrypted on save

For binary files that need a real path on disk (PPT, PDF, images, video), an explicit unlock / lock toggle makes the file plaintext during the window. The next joy auth (or any other crypto-touching joy command) opportunistically re-locks anything you forgot.

Once the passphrase is entered, every read and write command in the same joy invocation uses the unlocked zone keys without re-prompting. joy ls always lists every item in the project: those in a zone you cannot decrypt appear as a locked row (*** in the typed columns, [encrypted, no access] as the title) with the zone surfaced in an ENC column. Non-interactive callers can supply the passphrase via JOY_PASSPHRASE.

The event log records only structural facts (timestamp, item ID, action, actor); titles, descriptions, and comment text never enter the log. That preserves the audit trail when items are encrypted later: no historical leak, no retroactive log redaction.

Crypt is tied to Auth: as long as you can recover your identity, every zone you have a wrap for remains decryptable. There is no separate Crypt secret to lose.

AI Tool delegation tokens with Crypt scope (ADR-041). Each operator who delegates to an AI Tool has a per-(operator, AI) delegation keypair derived deterministically from the operator's seed, with the public part registered once in project.yaml and the private part never persisted. Tokens are auth-only by default; --crypt embeds the delegation private key so the AI can unwrap zone keys for the duration of the token. Session expiry is clamped to token expiry, so a 30-minute Crypt token grants exactly 30 minutes:

joy auth token add ai:claude@joy                       # auth-only, default 24h
joy auth token add ai:claude@joy --crypt --ttl 30m     # auth + crypt, 30 min
joy crypt grant   ai:claude@joy --zone customer-x      # per-operator wraps
joy crypt revoke  ai:claude@joy --zone customer-x      # remove all of them

Token issuance writes nothing to project.yaml: signatures are produced from the operator's seed at passphrase entry and the wraps were already in place from an earlier joy crypt grant.

Joyint as optional decrypt gateway. A project owner can grant Joyint a per-zone platform-wrap from the Joyint Web UI (lands with MS-03 WebUI Crypt parity). With the wrap, Joyint can render decrypted content for authenticated reviewers in its UI without persisting plaintext, and decrypt-on-mirror to secondary forges (joy int mirror add ... --decrypt). Other forges never receive a wrap; there is no Joy-aware decrypt service on GitHub. You choose per zone, per mirror.

AI Integration

Register AI tools as project members with identity, capabilities, and delegation tracking:

joy ai init                              # Configure AI tool integration
joy project member add ai:claude@joy --capabilities "implement,review"
joy auth token add ai:claude@joy         # Issue a delegation token to the AI
joy ai rotate ai:claude@joy              # Rotate the AI's delegation keypair

AI actions are traced in the event log with delegation chains showing who authorized each action.

Capabilities and Interaction Levels

Members are added with an explicit capability set. The default is the lifecycle set plus create and assign; manage and delete must be granted explicitly. AI members never get manage at runtime, even if listed in project.yaml.

Each capability also carries an interaction level (autonomous, supervised, collaborative, interactive, pairing) resolved across four layers: project defaults, project overrides, personal preference, item override. AI tools read the resolved level via joy project member show <ID> and follow it.

joy project member add ai:claude@joy --capabilities "implement,review,document"
joy project member show ai:claude@joy   # Effective levels per capability

Gates (Status Rules)

Status transitions are unrestricted by default. Add gates per transition in .joy/project.yaml when the project needs them:

status_rules:
  review_to_closed:
    allow_ai: false

Today allow_ai is enforced at runtime. Other rule kinds (requires_role, requires_ci) are part of the vision and not yet enforced.

Project Configuration

Layered configuration: personal overrides, project defaults, and built-in fallbacks:

joy config                       # Show all resolved values
joy config set output.emoji true # Set a personal override
joy project set language de      # Change project language

Machine-Readable Output

Every command accepts a global --json flag. Default output stays human-readable; --json switches to a stable, structured envelope ({"version": 1, "data": ...}) so scripts and CI never have to scrape display text:

joy ls --json                              # Same as joy --json ls
joy show JOY-0001 --json
joy --json ls | jq '.data.items[].id'

Within a major release, fields are additive only - consumers can rely on the keys they already use.

Commit-Msg Hook

Installed via joy init. Enforces that every commit message references at least one item ID:

git commit -m "feat(db): add migration CB-0005"     # OK
git commit -m "chore: bump dependencies [no-item]"   # OK (explicit opt-out)
git commit -m "fix typo"                              # REJECTED

Self-Update and Auto-Sync

joy update is the single entry point for keeping joy current. It swaps the binary (when joy was installed by the cargo-dist installer) and refreshes everything joy manages inside the repo: .gitattributes, the YAML merge driver registration, the commit-msg hook, embedded defaults, SECURITY.md, the project.yaml schema, and configured AI tool instruction files (CLAUDE.md, COPILOT.md, QWEN.md, AGENTS.md).

joy update                       # Swap binary + refresh in-repo state
joy update --check               # Read-only audit of every joy-managed artefact
joy update --no-binary           # In-repo refresh only
joy update --json                # Same, machine-readable envelope

The in-repo half is implicit by default: every joy invocation compares its own version against joy.last-sync-version (kept in the clone's local git config) and silently catches up when they differ. Users see a single joy X.Y.Z: synced this repo (...) line on stderr. Set auto-sync: false in .joy/config.yaml to opt out per project.

A downgrade guard keeps an older joy binary from rolling repo state back when the marker is ahead of the running binary: joy warns, skips the in-repo refresh, and asks the user to update joy first.

See the Updating joy tutorial for the full flow.

Coming Next Planned

TUI

Interactive terminal UI for browsing and editing the backlog. Keyboard-driven, real-time navigation.

Notifications

Get notified about status changes, mentions, blocked items, and milestone risks.

Full-text search across item titles, descriptions, and comments with regex support.

Custom Fields

Define project-specific fields on items for domain-specific tracking needs.