▍ humdrum codex / custard v0.3.0
license AGPL-3.0

docs: jj bookmark-mode release guide + git→jj migration section

ad0ec0d85cdaa8aa9c25799c9980f001ef13430c
humdrum <me@humdrum.me> · 2026-06-19 09:24

parent 256fb703

docs: jj bookmark-mode release guide + git→jj migration section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

3 files changed

README.md +1 −0
@@ -175,5 +175,6 @@ ## Docs
 
 - **[docs/MANUAL.md](docs/MANUAL.md)** — operating manual: everyday git, privacy, versioning, issues, the deploy CLI, brew releases, updating the forge.
 - **[docs/MIGRATION.md](docs/MIGRATION.md)** — moving a repo GitHub → Soft + custard (static/Pages → server, Vercel, CLIs, libraries).
+- **[docs/JUJUTSU.md](docs/JUJUTSU.md)** — using jujutsu (jj) with Soft + custard: bookmark-mode releases and how each jj operation appears in the forge.
 
 See `PLAN.md` for the phased build plan.
docs/JUJUTSU.md +127 −0
@@ -0,0 +1,127 @@
+# Using jujutsu (jj) with Soft Serve + custard
+
+[jujutsu](https://github.com/martinvonz/jj) (`jj`) is a Git-compatible VCS that uses git as its
+storage backend. This means it works naturally with Soft Serve and custard — no special server
+support required. Replace `your-host` with your forge domain throughout.
+
+## What works unchanged
+
+jj stores its history in a standard git repo. `jj git clone` and `jj git push` speak plain git to
+Soft Serve over SSH (port 23231). Once pushed, every object — commits, trees, blobs, branches —
+is ordinary git data. custard reads that data the same way it reads any git repo: commits show in
+the log, file trees browse normally, blobs highlight with syntax color, refs list cleanly.
+
+custard never knows whether a repo was authored in jj, stock git, or any other tool. There is
+nothing to configure on the forge side.
+
+```sh
+# clone via jj
+jj git clone soft:<repo>
+
+# push changes
+jj git push --branch main      # or --all
+```
+
+## Concept mapping
+
+| jj concept | What lands in git / custard |
+|---|---|
+| commit | git commit — appears in log, tree, blob views |
+| bookmark | git branch — appears on the refs page under Branches |
+| `jj describe` | edits the commit message of the working commit |
+| `jj git push --bookmark` | pushes a branch to Soft Serve |
+| change ID (`abc123...`) | local-only; never pushed, never visible in custard |
+| op log | local-only; the forge has no concept of it |
+| annotated tag | not supported (see below) |
+
+jj change IDs and the operation log are entirely local. They exist inside your `.jj/` directory and
+are never transmitted over git push — so custard never sees them and has no way to display them.
+
+## The tag gap
+
+jj cannot create or move annotated git tags. This matters because custard's Homebrew release
+pipeline is triggered when a tag is pushed (via a Soft Serve webhook), and the version pill on a
+repo's page normally reads from the highest semver tag.
+
+For a pure-jj repo the fix is **bookmark mode**: instead of tagging a commit to publish a release,
+you create or move a `release/vX.Y.Z` bookmark (a plain git branch) to the commit you want to
+ship, then push it. custard detects the bookmark push and treats it as a release.
+
+Tag-based repos are unaffected — bookmark mode is opt-in.
+
+## Cutting a release in bookmark mode
+
+### One-time setup
+
+Add `brew.source: bookmark` to `.custard.yaml` in the repo:
+
+```yaml
+brew:
+  enabled: true
+  package: "."              # or ./cmd/<name>
+  source: bookmark          # opt in to bookmark-mode releases
+  # bookmark_prefix: release/  # default; change only if you need a different prefix
+```
+
+Register the release webhook (same as tag mode):
+
+```sh
+ssh soft repo webhook create <name> https://your-host/hooks/release \
+  -e branch_tag_create -c json -s "$WEBHOOK_SECRET"
+```
+
+### Per-release workflow
+
+```sh
+# 1. Finish the commit that will ship
+jj describe -m "Add the new feature"
+
+# 2. Create or move the release bookmark to the current working commit
+jj bookmark set release/v1.2.0
+
+# 3. Push the bookmark
+jj git push --bookmark release/v1.2.0
+```
+
+The webhook fires on the branch push. custard strips the prefix (`release/`) to extract the
+version (`v1.2.0`), builds the tarball, writes the formula to the `homebrew-tap` repo, and publishes.
+The version pill on the repo's page shows `v1.2.0`, labelled with the tip commit's subject line (jj
+bookmarks carry no annotated message — the commit subject stands in).
+
+Versions must be `vMAJOR.MINOR.PATCH` (no pre-release suffixes).
+
+### Installing
+
+```sh
+brew tap you/tap https://your-host/git/homebrew-tap.git
+brew trust you/tap
+brew install <name>
+brew upgrade <name>
+```
+
+## How each jj operation shows up in custard
+
+| jj operation | Effect in custard |
+|---|---|
+| `jj commit` / `jj describe` + push | new commit(s) appear in log; tree/blob views update |
+| `jj bookmark set <name>` + push | branch appears (or updates) on the refs page |
+| `jj bookmark set release/vX.Y.Z` + push | release publishes; version pill and tap formula update |
+| history rewrite + `jj git push --force` | custard reads the new state (read-only, safe) |
+| `jj git push --all` | all bookmarks land on Soft; all show on the refs page |
+| change ID / op log | local-only; never visible in custard |
+
+## What's intentionally not supported
+
+**No auto-detection.** custard cannot infer from the objects in a repo that it was authored with
+jj. Bookmark mode must be enabled explicitly with `brew.source: bookmark` in `.custard.yaml`.
+
+**One mode at a time.** A repo is in either tag mode (default) or bookmark mode. Switching is a
+one-way config flip — see [MIGRATION.md](MIGRATION.md) for the procedure. After the flip, the tag
+path stops triggering releases and the bookmark path takes over. The two modes never mix, and
+there is no cross-mode version comparison.
+
+**Scope limit — the index list.** The version shown for each repo on the forge's main index (repo
+list) is always derived from the highest semver git tag. That code path does not read `.custard.yaml`
+and is not mode-aware. For a pure-jj repo this means the index list shows no version; the
+mode-aware version pill lives on each repo's own page and refs page, which is the primary surface.
+This is a deliberate tradeoff and is not expected to change unless the index read is refactored.
docs/MIGRATION.md +42 −0
@@ -115,3 +115,45 @@ git remote add github git@github.com:you/<name>.git
 git push github --mirror      # occasionally
 ```
 (Then you're primary-on-Soft, not fully off GitHub.)
+
+---
+
+## Switching a repo from git tags to jj bookmarks
+
+If you move a repo's workflow to [jujutsu](https://github.com/martinvonz/jj), you lose the ability
+to create annotated git tags — which is how custard's Homebrew release pipeline normally triggers.
+Bookmark mode lets jj repos publish releases from `release/vX.Y.Z` bookmarks (git branches) instead.
+
+This is an intentional, one-way flip. Do not switch back and forth; the two modes never mix.
+
+### The config change
+
+In `.custard.yaml`, set `brew.source: bookmark`:
+
+```yaml
+brew:
+  enabled: true
+  package: "."
+  source: bookmark
+  # bookmark_prefix: release/  # default; omit unless you need a different prefix
+```
+
+Commit and push. After the flip, the tag-based trigger stops firing — future tag pushes are ignored
+for release purposes. Releases come only from `release/*` bookmark pushes going forward.
+
+### Version continuity and the downgrade guard
+
+Past tag-based releases already published to the tap are unaffected; users with those versions
+installed can still `brew upgrade` once the first bookmark release lands.
+
+The downgrade guard compares the incoming bookmark version against the highest already-published
+**release bookmark** (not the old tags). This means: after flipping, the **first bookmark release
+must be at or above the highest tag version you already shipped**. If your last tag was `v1.3.0`,
+the first `release/vX.Y.Z` bookmark must be `v1.3.0` or higher or the webhook will reject it.
+
+Plan the cutover version accordingly — bumping the patch is fine: `v1.3.1` works.
+
+### Day-to-day workflow after the flip
+
+See [JUJUTSU.md](JUJUTSU.md) for the full jj usage guide, including how each operation appears in
+custard and the per-release push workflow.