Reusable jj + Claude Code workflow template — per-session snapshot management for concurrent AI agents
Find a file
Sid c32ae6c99e feat(planctl): docs + close-out
* README.md: new `## planctl` section after the jj-commitd section —
  description, `go install` command, classification-code list,
  pointer to the plan dir.
* AGENTS.md: bullet under `dev/ workflow` pointing agents at
  `planctl lint` as a pre-codex sanity gate.
* dev/README.md: "Pre-codex planctl lint gate" paragraph in the
  Skill workflow section.
* tasks.md: appended full traceability matrix — one row per PRD
  R-id and one per design D§ id, with the tasks and tests covering
  each. Grep-verifiable against the `// spec:planctl/*` anchors.
* Dogfood pass (task 9.5) against dev/plans/26172-planctl/ flushed
  out real self-violations in the plan docs:
    - Expanded en-dash range tags (`R1.1–R1.7`) to explicit
      comma-separated lists.
    - Dropped comma-containing annotations (`D§0 (D-2, D-5)` →
      `D§0`).
    - Added D§2 / D§3 / D§7 umbrella citations to tasks 5.3 /
      3.1 / 7.7.
    - Applied the design §3.4 bold-prefix EARS exemption to R5.3
      / R5.4 / R5.5 / R5.11 (grammatically valid but non-strict
      EARS) by prepending short bold labels.
    - Updated PRD R5.11 to say `plan-directory basename` instead
      of `relative path from CWD` — resolving a long-standing
      divergence with task 6.4 + design §3.7 + the implementation.
* Final dogfood: `26172-planctl: clean (79 tasks, 45 requirements,
  21 design sections)`.

Parent task 9.0 from dev/plans/26172-planctl/tasks.md.
Codex code-review session: 019db302-b8a9-7e53-b1e3-feab4ed7081f (2 rounds).
2026-04-21 20:39:08 -06:00
.claude dev: add spec-driven workflow, agent skills, and AGENTS.md convention 2026-04-19 23:37:12 -06:00
.forgejo/workflows feat(planctl): CI workflow + perf benchmark 2026-04-21 20:14:06 -06:00
cmd feat(planctl): CI workflow + perf benchmark 2026-04-21 20:14:06 -06:00
dev feat(planctl): docs + close-out 2026-04-21 20:39:08 -06:00
scripts feat(commitd): legacy cleanup, conflict detection, session inventory, configurable reap/debounce 2026-03-29 03:50:06 -06:00
.gitignore feat(planctl): project skeleton — cmd/planctl, goldmark dep, subcommand dispatch 2026-04-21 17:02:59 -06:00
AGENTS.md feat(planctl): docs + close-out 2026-04-21 20:39:08 -06:00
CLAUDE.md dev: add spec-driven workflow, agent skills, and AGENTS.md convention 2026-04-19 23:37:12 -06:00
go.mod feat(planctl): scanner — goldmark-backed InCode + LineMask classification 2026-04-21 17:02:59 -06:00
go.sum feat(planctl): project skeleton — cmd/planctl, goldmark dep, subcommand dispatch 2026-04-21 17:02:59 -06:00
INTEGRATION.md dev: add spec-driven workflow, agent skills, and AGENTS.md convention 2026-04-19 23:37:12 -06:00
README.md feat(planctl): docs + close-out 2026-04-21 20:39:08 -06:00

jj-template

Reusable jj + Claude Code workflow template. Provides automatic per-session snapshot management so every Claude Code session's work is committed and isolated — even with concurrent agents.

What It Does

  • Session start: snapshots any pre-existing uncommitted work before the agent starts editing
  • Post-edit: tracks every file Claude edits (via Edit/Write/NotebookEdit tools) per-session
  • Session end: commits tracked files with a descriptive wip(claude:<session>): message, then commits any untracked leftovers
  • Concurrent safety: multiple agent sessions on the same repo get isolated, file-scoped commits
  • Bookmarks: each session's commit gets a wip/claude-{session} bookmark for easy reference
  • Base tracking: records the starting revision per session (visible in status)
  • Orphan reaping: dead sessions (crashed CLI) are cleaned up automatically via PID liveness checks and 30m timeout (configurable via JJ_HOOK_STALE_MIN)
  • Legacy cleanup: daemon sweeps orphaned /tmp/jj-claude-*-files from pre-daemon hook versions on startup
  • Conflict detection: warns when multiple sessions edit the same file
  • Squash: squash-wip consolidates accumulated wip commits into one for cleaner history
  • Auto-squash: set JJ_HOOK_AUTO_SQUASH=1 to squash automatically at session end
  • Repo cleanup: /repo-cleanup Claude command organizes wip commits into themed conventional commits

Commit Daemon (jj-commitd)

A Go daemon that batches file edits into debounced commits via a Unix socket.

  • How it works: session-start launches the daemon; post-edit sends file paths over a socket; a debounce timer (default 3s) batches edits into a single jj commit; session-end flushes pending commits and shuts down
  • Build: go build -o bin/jj-commitd ./cmd/jj-commitd/ or go install ./cmd/jj-commitd/
  • Config: JJ_HOOK_DEBOUNCE_SEC (default 3), JJ_HOOK_STALE_MIN (default 30)
  • Session inventory: on session-start, returns list of other active sessions and their files
  • Logging: daemon log at /tmp/jj-commitd-{repo_id}.log
  • Fallback: if the binary is not found, the hook falls back to file-tracking-only mode (commits at session end)

planctl

Lint tool for spec-driven plan directories (dev/plans/<YYWWD>-<feature-slug>/). Validates traceability tags, cross-references between prd.md / design.md / tasks.md, file-presence at close-out, and EARS-keyword conformance on PRD acceptance criteria. Read-only by design — v1 ships planctl lint and nothing else.

  • Install: go install forgejo.zerova.net/sid/template-jj/cmd/planctl@latest
  • Usage: planctl lint [plan-dir] — pass a path, or invoke from anywhere inside a plan dir or a repo containing dev/plans/.
  • Output: one line per diagnostic in the form <path>:<line>: [<severity>] <code>: <message>; a clean plan prints one summary line. --format=json emits JSONL for tooling.
  • Flags: --strict (warnings → exit 1), --no-ears (skip EARS check), --format={text,json}, --color={auto,always,never} (no-op in v1).
  • Classification codes: tag-syntax, tag-unclosed, orphan-requirement, orphan-design, uncovered-requirement, uncovered-design, missing-prd, missing-closeout-file, ears-violation. These are grep-stable public API.
  • Spec: see dev/plans/26172-planctl/ for the PRD, design, and task list that defined the tool.

Prerequisites

  • jj (Jujutsu VCS)
  • jq (JSON processor)
  • Claude Code CLI
  • Go 1.22+ (optional, for building the commit daemon)

Quick Start

See INTEGRATION.md for step-by-step instructions to add this to an existing or new project.

Files

cmd/jj-commitd/
  main.go              # Commit daemon (Go)
scripts/
  _lib.sh              # Shared utilities (REPO_ROOT resolution)
  jj-hook.sh           # Main hook script (all 6 actions)
  test-jj-hooks.sh     # 26-test integration suite
.claude/
  settings.json        # Hook configuration for Claude Code (team-shared)
  commands/
    repo-cleanup.md    # /repo-cleanup slash command

Manual Commands

# Check active sessions for this repo
./scripts/jj-hook.sh status

# Squash all wip commits between main and @ into one
./scripts/jj-hook.sh squash-wip

# Force-reap all non-alive sessions
./scripts/jj-hook.sh reap

How It Works

Tracking Files

Each active session gets a tracking file at /tmp/jj-claude-{REPO_ID}-{SESSION_ID}-files. The REPO_ID is a cksum of the repo root path, preventing cross-repo contamination. Companion files record the parent process (-pid) and starting revision (-base) for liveness checking and cleanup context.

Commit Flow

With daemon (default when jj-commitd binary is available):

session-start             →  snapshot pre-existing work
                          →  launch jj-commitd daemon (or connect to existing)
                          →  record base revision, reap stale sessions

post-edit (per file)      →  send file path to daemon via Unix socket
                          →  daemon resets debounce timer (3s default)
                          →  on timer expiry: commit all batched files

session-end               →  flush pending commits (daemon commits immediately)
                          →  set wip/claude-{session} bookmark
                          →  commit untracked leftovers (if sole session)
                          →  auto-squash (if JJ_HOOK_AUTO_SQUASH=1)
                          →  clean up tracking + PID + base files

Without daemon (fallback):

session-start             →  snapshot pre-existing work
                          →  record base revision, initialize tracking file
                          →  reap stale/orphaned sessions

post-edit (per file)      →  append relative path to tracking file (dedup)

session-end               →  commit tracked files (file-scoped)
                          →  set wip/claude-{session} bookmark
                          →  commit untracked leftovers (if sole session)
                          →  auto-squash (if JJ_HOOK_AUTO_SQUASH=1)
                          →  clean up tracking + PID + base files

Concurrency

When multiple Claude sessions edit the same repo simultaneously:

  • Each session tracks its own files independently
  • Session A ending only commits files A touched
  • Untracked changes (Bash-created files) are only committed when the last session ends
  • If both sessions touch the same file, the first to end commits it; the second skips it (no diff remaining)

Tests

scripts/test-jj-hooks.sh

Runs 34 integration tests in temporary jj repos. Requires jj and jq. Portable across macOS and Linux.