Document jj integration and add a tag-triggered release workflow
The README gains a "Using with jj" section with the merge-tools config
d3j drops into, and its status section catches up with reality: the
pipeline and CLI work end to end. The release workflow follows the
ranger repo's shape — a v* tag builds aarch64 macOS and Linux binaries,
publishes a GitHub release, and pushes a DotSlash file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWq3C7nEepKJBPoJhVKZMu
change
commit 015b399349de95ce1c70ca7e4807aefb711c7b59
author Claude <noreply@anthropic.com>
date
parent 93e66b7a
diff --git a/.github/workflows/dotslash-config.json b/.github/workflows/dotslash-config.json
new file mode 100644
index 0000000..428f237
--- /dev/null
+++ b/.github/workflows/dotslash-config.json
@@ -0,0 +1,18 @@
+{
+  "outputs": {
+    "d3j": {
+      "platforms": {
+        "macos-aarch64": {
+          "regex": "^d3j-aarch64-apple-darwin",
+          "format": "tar.gz",
+          "path": "d3j"
+        },
+        "linux-aarch64": {
+          "regex": "^d3j-aarch64-unknown-linux-gnu",
+          "format": "tar.gz",
+          "path": "d3j"
+        }
+      }
+    }
+  }
+}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..32bb9ef
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,83 @@
+name: Release
+
+on:
+  push:
+    tags:
+      - 'v*'
+
+permissions: {}
+
+jobs:
+  build-macos:
+    runs-on: macos-latest
+    steps:
+      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+        with:
+          persist-credentials: false
+
+      - name: Build macOS
+        run: |
+          cargo build --release
+          tar -czf d3j-aarch64-apple-darwin.tar.gz -C target/release d3j
+
+      - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+        with:
+          name: d3j-aarch64-apple-darwin
+          path: d3j-aarch64-apple-darwin.tar.gz
+
+  build-linux:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+        with:
+          persist-credentials: false
+
+      - name: Build Linux
+        run: |
+          cargo install cross --git https://github.com/cross-rs/cross
+          cross build --release --target aarch64-unknown-linux-gnu
+          tar -czf d3j-aarch64-unknown-linux-gnu.tar.gz -C target/aarch64-unknown-linux-gnu/release d3j
+
+      - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+        with:
+          name: d3j-aarch64-unknown-linux-gnu
+          path: d3j-aarch64-unknown-linux-gnu.tar.gz
+
+  publish:
+    needs: [build-macos, build-linux]
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    steps:
+      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+        with:
+          persist-credentials: false
+
+      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+        with:
+          merge-multiple: true
+
+      - name: Publish
+        run: |
+          gh release create "${GITHUB_REF_NAME}" \
+            --title "${GITHUB_REF_NAME}" \
+            --generate-notes \
+            d3j-aarch64-apple-darwin.tar.gz \
+            d3j-aarch64-unknown-linux-gnu.tar.gz
+        env:
+          GH_TOKEN: ${{ github.token }}
+
+  dotslash:
+    needs: publish
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+    steps:
+      # dotslash-publish-release needs credentials to push a commit.
+      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+      - uses: facebook/dotslash-publish-release@2539c4d8ae00a42773306c8731d2dd3724d979d2 # v1
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        with:
+          config: .github/workflows/dotslash-config.json
+          tag: ${{ github.ref_name }}
diff --git a/README.md b/README.md
index d0f87a5..de0edfd 100644
--- a/README.md
+++ b/README.md
@@ -22,15 +22,12 @@ any tree-sitter grammar, and ships with Rust, Java, and JSON.
 
 ## Status
 
-Early development. The crate is scaffolded and two pieces are in place:
-
-- the language registry (`Lang`), which detects a grammar by file
-  extension and loads its `node-types.json` metadata
-- the library-wide error space (`Error`)
-
-The diff, merge, checker, and synthesis stages — and the working
-CLI — are not built yet. The command-line interface described below is
-the planned shape, not a working one. See
+Early development, but the pipeline works end to end: parsing, the
+arena AST, base-to-branch diffing, the pushout merge, the correctness
+checker, output synthesis, and the `d3j` CLI are all in place for the
+bundled grammars (Rust, Java, JSON). Move detection, deeper Java
+conflict rules, and evaluation against the paper's replication
+datasets are still to come. See
 [`docs/plans/2026-07-10-d3j-design.md`](docs/plans/2026-07-10-d3j-design.md)
 for the full design and milestones.
 
@@ -67,7 +64,7 @@ To install the `d3j` binary:
 just install    # cargo install --locked --path .
 ```
 
-## Usage (planned)
+## Usage
 
 The binary takes diff3-style argument order, so it drops into a
 merge-driver configuration:
@@ -83,6 +80,40 @@ Both exit 2 on unparsable input or an unknown language — d3j never
 silently falls back to a textual merge. Language is detected from the
 file extension, with `--lang` as an override.
 
+## Using with jj
+
+d3j plugs into [Jujutsu](https://jj-vcs.dev) as a merge tool. With the
+binary installed, add this to your jj config (`jj config edit --user`):
+
+```toml
+[merge-tools.d3j]
+program = "d3j"
+merge-args = ["merge", "$base", "$left", "$right", "-o", "$output"]
+merge-conflict-exit-codes = [1]
+conflict-marker-style = "git"
+```
+
+Then resolve conflicts with it:
+
+```sh
+jj resolve --tool d3j                   # all conflicted files
+jj resolve --tool d3j 'glob:**/*.rs'    # scope to one language
+```
+
+jj hands d3j temporary copies of the three sides (the files keep their
+extension, so language detection works) and reads the merged result
+back from `$output`. `merge-conflict-exit-codes` (jj ≥ 0.24) tells jj
+that exit 1 means the output still contains conflict markers, which jj
+parses back into its own conflict state; `conflict-marker-style =
+"git"` matches the diff3-style markers d3j emits. Exit 2 is
+deliberately not listed: an unparsable file or unknown language is a
+tool failure, and jj leaves the conflict untouched.
+
+Because only Rust, Java, and JSON grammars are bundled, scoping
+`jj resolve` with a fileset is the smoothest workflow — files in other
+languages fail with exit 2. Keep `ui.merge-editor` pointed at an
+interactive editor and reach for `--tool d3j` explicitly.
+
 ## Development
 
 The `justfile` wraps the common tasks:
@@ -97,6 +128,13 @@ just mutants    # mutation testing
 CI runs formatting, clippy, coverage, and mutation testing on every push
 and pull request.
 
+## Releases
+
+Pushing a `v*` tag triggers the release workflow, which builds macOS
+(aarch64) and Linux (aarch64) binaries, creates a GitHub release with
+generated notes, and publishes a [DotSlash](https://dotslash-cli.com)
+file for the release.
+
 ## Reference
 
 - Paper: Mori & Hashimoto, "On the Correctness of Software Merge,"