# Integration Guide Instructions for adding the jj + Claude Code workflow to an existing or new project. Written for agentic devs (Claude, Codex, etc.) to follow. ## Workflow Layers This template ships **two independent layers**; adopt them together (recommended) or separately. 1. **Session-commit tooling** — `cmd/`, `scripts/`, `.claude/settings.json`, `.claude/commands/`. The `jj-commitd` daemon + hook scripts + `/repo-cleanup` slash command give every Claude Code session its own isolated commit stack via debounced per-session commits. Documented in the "Building the Daemon", "New Project", and "Existing Project" sections below. 2. **Spec-driven workflow** — `AGENTS.md`, `CLAUDE.md`, `dev/`, `.claude/skills/`. Portable cross-agent conventions: YYWWD-prefixed plan directories, the `/create-prd` → `/create-design` → `/generate-tasks` → `/process-task-list` chain, peer-reviewer CLI skills (`codex`, `copilot`, `gemini`, `kiro`), multi-agent workspace guidance, optional upstream-leak pre-push guard. Documented in the "Spec-Driven Workflow" section at the bottom of this file. The layers are orthogonal. You can adopt only the session-commit tooling (existing users pre-dating the workflow layer), only the spec-driven workflow (projects that manage commits manually), or both. ## Prerequisites - [jj](https://martinvonz.github.io/jj/latest/install/), [jq](https://jqlang.github.io/jq/), [Claude Code](https://docs.anthropic.com/en/docs/claude-code) - [Go](https://go.dev/dl/) 1.22+ (optional, for the commit daemon) ## Building the Daemon (Optional) The commit daemon provides debounced commits during editing. Without it, the hook falls back to committing at session end only. ```bash cd /path/to/jj-template go install ./cmd/jj-commitd/ # Binary installed to $GOPATH/bin/jj-commitd (ensure it's in PATH) ``` The hook script auto-starts the daemon on `session-start` if the binary is found. ## New Project ```bash # 1. Create your project and init jj mkdir my-project && cd my-project jj git init # 2. Copy the template files cp -r /path/to/jj-template/scripts ./scripts cp -r /path/to/jj-template/.claude ./.claude # Note: settings.json is team-shared (committed to repo) chmod +x scripts/jj-hook.sh scripts/test-jj-hooks.sh # 3. Commit the tooling jj commit -m "chore: add jj + claude code workflow" jj bookmark set main -r '@-' # 4. Verify scripts/test-jj-hooks.sh ``` ## Existing Project ### Step 1: Add Scripts Copy `scripts/jj-hook.sh`, `scripts/_lib.sh`, and `scripts/test-jj-hooks.sh` into your project's `scripts/` directory (create it if needed). ```bash mkdir -p scripts cp /path/to/jj-template/scripts/_lib.sh scripts/ cp /path/to/jj-template/scripts/jj-hook.sh scripts/ cp /path/to/jj-template/scripts/test-jj-hooks.sh scripts/ chmod +x scripts/jj-hook.sh scripts/test-jj-hooks.sh ``` If your project already has a `scripts/_lib.sh`, merge in the `REPO_ROOT` line: ```bash REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ``` That's the only variable `jj-hook.sh` requires from `_lib.sh`. ### Step 2: Add Claude Code Hooks Copy `.claude/settings.json` into your project, or merge the hooks into your existing settings: ```json { "hooks": { "SessionStart": [ { "matcher": "", "hooks": [ { "type": "command", "command": "./scripts/jj-hook.sh session-start", "timeout": 15 } ] } ], "SessionEnd": [ { "matcher": "", "hooks": [ { "type": "command", "command": "./scripts/jj-hook.sh session-end", "timeout": 15 } ] } ], "PostToolUse": [ { "matcher": "Edit|Write|NotebookEdit", "hooks": [ { "type": "command", "command": "./scripts/jj-hook.sh post-edit", "timeout": 10 } ] } ] } } ``` **Note**: `settings.json` is committed to the repo and shared across the team. Use `.claude/settings.local.json` for personal overrides (it's gitignored by default). ### Step 3: Add Repo Cleanup Command (Optional) Copy `.claude/commands/repo-cleanup.md` to get the `/repo-cleanup` slash command: ```bash mkdir -p .claude/commands cp /path/to/jj-template/.claude/commands/repo-cleanup.md .claude/commands/ ``` Customize the theme categories in that file to match your project structure. ### Step 4: Verify ```bash # Run the test suite scripts/test-jj-hooks.sh # Start a Claude session and check status ./scripts/jj-hook.sh status # View daemon logs (if daemon is running) tail -f /tmp/jj-commitd-$(printf '%s' "$PWD" | shasum -a 256 | cut -c1-12).log ``` ## Converting a Git Repo to jj If your project uses git and you want to adopt jj: ```bash cd my-project jj git init --colocate # colocate keeps .git alongside .jj jj bookmark set main -r '@-' ``` Colocated mode lets you use both `git` and `jj` commands. The hook script works with both colocated and standalone jj repos. ## Customization ### Different Script Location If your scripts live somewhere other than `scripts/`, update the paths in `.claude/settings.json`: ```json "command": "./tools/jj-hook.sh session-start" ``` And update the `_lib.sh` source line in `jj-hook.sh`: ```bash source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh" ``` ### Existing `_lib.sh` The hook only needs `REPO_ROOT` from `_lib.sh`. If your `_lib.sh` already sets `REPO_ROOT`, no changes needed — just ensure the double-source guard pattern: ```bash [[ -n "${_LIB_LOADED:-}" ]] && return 0 _LIB_LOADED=1 ``` ### Commit Message Format The default format is `wip(claude:): `. To change it, edit the `jj commit -m` lines in `jj-hook.sh` (search for `wip(claude:`). ### Main Branch Name The `squash-wip` command assumes a `main` bookmark. If you use a different name (e.g., `trunk`, `master`), update the `jj log -r 'main'` references in `jj-hook.sh`. ### Tuning Environment variables (set in your shell profile or `.claude/settings.local.json` hook commands): | Variable | Default | Description | |----------|---------|-------------| | `JJ_HOOK_DEBOUNCE_SEC` | `3` | Seconds to wait before batching edits into a commit (max 30) | | `JJ_HOOK_STALE_MIN` | `30` | Minutes before an idle session is reaped by the daemon | Example in hook command: ```json "command": "JJ_HOOK_DEBOUNCE_SEC=5 ./scripts/jj-hook.sh post-edit" ``` --- ## Spec-Driven Workflow The second template layer: portable cross-agent conventions that sit on top of whatever commit infrastructure you use. Works with or without the `jj-commitd` daemon above; works with or without jj (though the skills document jj-first commands). ### Step 1: Copy the portable files From the template root into your project: ```bash cp /path/to/template-jj/AGENTS.md . cp /path/to/template-jj/CLAUDE.md . cp -r /path/to/template-jj/dev . mkdir -p .claude && cp -r /path/to/template-jj/.claude/skills .claude/ chmod +x dev/tools/install-hooks.sh dev/tools/pre-push-guard.sh ``` The template's `CLAUDE.md` contains a pointer to `AGENTS.md`, Claude-specific sub-agent guidance, **and a "Template-maintenance" section with `jj-commitd` internals**. The first two travel cleanly; **drop the "Template-maintenance" section** once copied — those notes apply to the template's own repo, not your project. ### Step 2: Replace placeholder tokens The portable files use placeholder tokens so you can slot your project's specifics in. Find them: ```bash rg -n '<[A-Z_]+>' AGENTS.md CLAUDE.md dev/ .claude/skills/ ``` Replace each across the files (find-and-replace, a scripted `sed`, or ad-hoc as team members encounter them): | Token | Example values | |-------|----------------| | `` | `my-app`, `acme-core` | | `` | Zig, Rust, Python, Go, TypeScript | | `` | `pytest`, `npm test`, `go test ./...`, `cargo test` | | `` | `src/`, `lib/`, `pkg/` | | `` | `upstream-org/repo` (for forks), or leave unset for greenfield projects | The `` and `` tokens inside plan-directory paths are runtime-generated by the skills — leave those as-is. ### Step 3: Install the optional upstream-leak pre-push guard If this repo is a fork and `dev/` artifacts should never land on the upstream: ```bash export UPSTREAM_BLOCKED_PATTERN='upstream-org/upstream-repo' # add to .envrc or shell profile ./dev/tools/install-hooks.sh ``` The guard at `.git/hooks/pre-push` (symlinked to `dev/tools/pre-push-guard.sh`) enumerates every path touched by commits in the push range and refuses pushes touching `dev/` paths to any remote whose URL contains the pattern. Greenfield projects with no upstream-leak concern can skip this step — the guard is a no-op when `UPSTREAM_BLOCKED_PATTERN` is unset. ### Step 4: Choose a parallel-agent convention If multiple agents will run concurrently in this repo, pick the isolation level you need: **Option A — `jj workspace add` (preferred, true isolation)** Each agent gets its own working directory while sharing the same `.jj/` object store. Branch switches in one workspace never flip another's tree. ```bash # From the main clone: jj workspace add --name planner ../-planner jj workspace add --name impl ../-impl # Agent sessions each start in their own directory: # planner: cd ../-planner # impl: cd ../-impl # Inspect or remove workspaces: jj workspace list jj workspace forget planner ``` See `dev/README.md` → "Primary isolation: jj workspaces" for the full convention (plans commit vs. feature branch discipline, handoff squash, etc.). **Option B — env vars (same working directory)** When a single working directory is unavoidable, each agent exports its role and target bookmark. The spec-driven skills' `§0 Pre-flight` step checks for mismatches before any write: ```bash export AGENT_ROLE=planner export AGENT_BOOKMARK=feature/- ``` **Coming soon — `JJ_AGENT_FEATURE`** A future `JJ_AGENT_FEATURE` env var will label commits and bookmarks with a human-readable feature name (e.g. `wip(auth:abc12345)`, bookmark `feat/auth`) so multi-agent history is readable at a glance without decoding session UUIDs. Track `docs/jj-commitd.md` for availability. Solo projects can skip this step — the convention matters only when two or more agents share a repo. ### Step 5: Verify ```bash # Confirm the eight skills are discoverable by Claude Code: ls .claude/skills/ # Should list: codex copilot create-design create-prd gemini generate-tasks kiro process-task-list # Confirm dev/ layout: ls dev/ # Should list: QUICKSTART.md README.md notes plans research tools # If you installed the upstream guard, verify the symlink: ls -la .git/hooks/pre-push # Should show a symlink to ../../dev/tools/pre-push-guard.sh # Claude Code should now recognize /create-prd, /create-design, /generate-tasks, /process-task-list, # and the per-tool skills (codex, copilot, gemini, kiro) as slash commands. ``` ### Step 6: Drive your first feature From a Claude Code session, type `/create-prd` and describe the feature. The skill chain will walk you through PRD → (optional design) → tasks → implementation, with codex peer-reviews between stages. See `dev/QUICKSTART.md` for the short version. ### Mixing with other agent tools The `.claude/skills/` pack is readable documentation even for tools that don't natively load skills: - **GitHub Copilot CLI** reads `AGENTS.md` automatically. The skills are referenced as docs it can fetch via `@.claude/skills/codex/SKILL.md` etc. - **Codex CLI**, **Gemini CLI**, **Kiro CLI** don't auto-load skill packs but respect `@`-file references in prompts. - **Cursor / other IDEs** typically read `AGENTS.md` or `.cursorrules`; point those at `AGENTS.md`. If you use multiple agents, per-tool session logs (`codex-sessions.md`, `copilot-sessions.md`, `gemini-sessions.md`, `kiro-sessions.md`) keep the review lineage unambiguous for each feature. --- ## Context-Window Token Awareness (planctl) Claude Code agents using `planctl` can receive threshold-based recommendations about their own context-window fill, so they can commit and start a fresh session *before* running out of tokens mid-task. The signal surfaces in every `planctl lint` invocation when session data is available. ### What the output looks like When context fill reaches 70%, 85%, or 95% of the limit, `planctl` appends a `context:` line after the plan summary: ```text 26174-planctl-context-tokens: clean (12 tasks, 6 requirements, 0 design sections) context: 142 k / 200 k tokens (71%) — plan to wrap up this session soon. ``` Band behavior: | Fill | Band | Severity | Suffix | |---|---|---|---| | < 70% | Normal | — | silent (no `context:` line) | | 70–84% | Info | `info` | `plan to wrap up this session soon.` | | 85–94% | Warn | `warn` | `commit current work and start a new session after this task.` | | ≥ 95% | Error | `error` | `stop new work; commit and close out immediately.` | Token counts are expressed in thousands rounded to the nearest integer (`143 k`, `200 k`); counts below 1000 display as `< 1 k`. ### JSON output With `--format=json`, the summary object gains a peer `context_window` key: ```json { "summary": { "plans": 1, "errors": 0, "warnings": 0 }, "context_window": { "tokens_used": 142000, "tokens_limit": 200000, "pct": 71, "severity": "info", "recommendation": "plan to wrap up this session soon." } } ``` In multi-plan output (e.g. a repo-root `planctl lint` that walks `dev/plans/**`), the `context:` line appears **once** after the aggregate `N plans linted, …` summary — context is session-level, not plan-level. ### When context is omitted - **No session data**: no env vars, no PID-keyed session file found → output is identical to the baseline (graceful degradation). - **Normal band (fill < 70%)**: no `context:` line emitted, to keep healthy sessions quiet. - **Unknown limit**: when `CLAUDE_CODE_MAX_CONTEXT_TOKENS` is unset, `planctl` emits a raw count (`context: 143 k tokens (limit unknown)`) and, in JSON, only the `tokens_used` field inside `context_window`. `context_window` **never shifts** the summary's `errors` / `warnings` counts — it's advisory output, not a lint diagnostic. ### Environment variables | Variable | Source | Purpose | |---|---|---| | `CLAUDE_SESSION_ID` | Hook (see below) or Claude Code | Primary session UUID | | `CLAUDE_CODE_MAX_CONTEXT_TOKENS` | Claude Code | Context-window limit (`200000` for Opus 4.7) | | `CLAUDE_PROJECT_DIR` | Claude Code | Project slug derivation for transcript lookup | If `CLAUDE_CODE_MAX_CONTEXT_TOKENS` is not set, `planctl` emits the raw count without a percentage or recommendation. **Hooks are optional**: with no hook and no env var, `planctl` degrades silently — same output as any other CLI tool. ### Hook integration (optional but recommended) `scripts/jj-hook.sh` already ships the integration. If you copied the template, you have it. The hook adds two behaviors: **`session-start` case** (template ships this — excerpt): ```bash FULL_SID=$(printf '%s\n' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null) if [[ "$FULL_SID" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then # Primary: propagate session ID to all subprocesses via env [ -n "${CLAUDE_ENV_FILE:-}" ] && \ echo "export CLAUDE_SESSION_ID=${FULL_SID}" >> "$CLAUDE_ENV_FILE" # Secondary: PID-keyed fallback for processes that lost env START_TICKS=$(planctl_start_ticks "$PPID") mkdir -p /tmp/planctl-sessions TMPF=$(mktemp /tmp/planctl-sessions/.tmp.XXXXXX) printf '%s\n%s\n' "$FULL_SID" "$START_TICKS" > "$TMPF" mv -f "$TMPF" "/tmp/planctl-sessions/${PPID}" fi ``` **`session-end` case** cleans up the PID-keyed file: ```bash rm -f "/tmp/planctl-sessions/${PPID}" ``` **`planctl_start_ticks` helper** — platform-specific process-start-time readback used by the PID-reuse defense in `cmd/planctl/context.go`: ```bash planctl_start_ticks() { local pid="$1" if [ -f "/proc/$pid/stat" ]; then # Linux: field 22 of /proc//stat is starttime in clock ticks since boot. # `comm` can contain ')' and spaces — strip up to and including the LAST ')'. local tail tail=$(sed 's/.*)//' "/proc/$pid/stat" 2>/dev/null) || { echo 0; return; } echo "$tail" | awk '{print $20}' | grep -E '^[0-9]+$' || echo 0 else # macOS: ps lstart → epoch seconds. ps -o lstart= -p "$pid" 2>/dev/null | xargs -I{} date -j -f '%a %b %d %T %Y' '{}' '+%s' 2>/dev/null || echo 0 fi } ``` The Linux branch writes *clock ticks since boot* and the Go resolver (`cmd/planctl/proc_linux.go`) reads the same unit from `/proc//stat` — units match within a platform. macOS uses epoch seconds on both sides via `kinfo_proc.kp_proc.p_starttime`. No cross-platform comparison is ever made. ### Troubleshooting - **`planctl` output never shows a `context:` line**: confirm you're running inside a Claude Code session. `echo $CLAUDE_SESSION_ID` should print a UUID. If not, check that the `SessionStart` hook fires and your shell sources `$CLAUDE_ENV_FILE`. - **Wrong percentage**: `CLAUDE_CODE_MAX_CONTEXT_TOKENS` may be mis-set. Without it, you'll see `(limit unknown)` instead of a percentage. - **Multiple concurrent sessions**: each agent's `planctl` resolves its own session via ancestor-PID walk, backed by `/tmp/planctl-sessions/` files. Liveness + start-stamp checks prevent cross-session contamination even across PID reuse. - **No hook installed**: `planctl` works without the hook — if neither `CLAUDE_SESSION_ID` is set nor a PID-keyed file is found during the 8-hop ancestor walk, the context line is silently omitted.